Skip to content

feat(admin-layer): relation pickers, enum-select guard, detail-link hook, 0.2.0 - #158

Merged
sksizer merged 8 commits into
mainfrom
admin-layer-pickers
Sep 23, 2026
Merged

sksizer merged 8 commits into
mainfrom
admin-layer-pickers

Conversation

@sksizer

@sksizer sksizer commented Sep 8, 2026

Copy link
Copy Markdown
Owner

Stacks on rust-ontogen#157 (the registry path option).

  • Relation picker. AdminRelationPicker.vue: a relation field lists the target entity's rows through its listMethod (first page for a paginated target), shows them as id — name, narrows as you type, and takes ArrowUp/ArrowDown/Enter/Escape. The raw id stays editable behind Edit id. relation-array keeps the chip input.
  • Enum select guard. The <select> renders only when field.enumValues is non-empty; a registry without values falls back to a text input.
  • Detail-link hook. useAdminDetailLink.ts exports ADMIN_DETAIL_LINK, an injection key for (entity, id) => { to, label } | null. A host that provides it gets a link in the detail page header next to Edit.
  • admin-types 0.2.0. idType is 'string' | 'number'.
  • Packing. Both packages are 0.2.0; the layer's peer on admin-types is ^0.2.0 (still optional, since bun auto-installs peers and the package is not on npm). just pack-admin packs both with pnpm into dist/; pnpm rewrites the layer's workspace:* devDependency to the version on the way out, so the tarball carries no workspace protocol.

Verified by vendoring the tarballs into a consumer (bun install, its registry unit test, and its Nuxt typecheck pass).

`ontogenAdmin.registry` names the generated admin registry the
`#admin-registry` alias resolves to. The default is unchanged — the
consuming app's `app/admin/generated/admin-registry` — so a host whose
registry is emitted into a Nuxt layer rather than the root app can point
the alias at it instead of copying the file.
…ook, 0.2.0

A relation field is a picker over the target entity's rows (type to
filter, arrow keys, Enter; the raw id stays behind "Edit id"). The enum
select renders only when the registry carries values, else text. A host
can provide ADMIN_DETAIL_LINK to link a detail page to its own page for
the row. admin-types widens idType to string | number. Both packages are
0.2.0; `just pack-admin` packs them with pnpm into dist/.
@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Sep 8, 2026 •

Copy link
Copy Markdown

Deploying ontogen with  Cloudflare Pages  Cloudflare Pages

Latest commit: 5f6c485
Status:⚡️  Build in progress...

View logs

…e by page

A paginated listMethod answers { items, total }; calling it bare and
filtering the envelope threw, and the table showed nothing.
…puts it

A list that takes no query struct is (limit?, offset?); passing
(undefined, limit, offset) asked every page for the default limit at
the wrong offset. The registry's listQuery says which shape a list has;
pageArgs builds the call from it.
Two statements in an inline handler need a separator; the SFC compiler
refused the template.
The edit form seeded a null relation as '' and sent it back, so saving
a row with no athlete tripped the foreign key. Blank now means none:
an emptied optional field goes out as null, which clears the column
(undefined would leave it alone). The layer is 0.2.1.
Base automatically changed from admin-registry-option to main September 19, 2026 05:39
* (`listQuery`), and the page goes after it.
*/
export function pageArgs(config: AdminEntityConfig, limit: number, offset: number): unknown[] {
return config.listQuery ? [undefined, limit, offset] : [limit, offset]

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: listQuery is never emitted, so query-taking paginated lists get the wrong argument shape.

pageArgs falls back to (limit, offset) unless config.listQuery is true, but nothing writes listQuery into a registry: src/clients/generators/admin.rs on this branch doesn't emit it at all, and main (6349a9d) emits the same flag under the name listHasQuery.

Scenario: a paginated entity whose Rust list takes a query struct gets the transport signature list(query?, limit?, offset?) (transport.rs puts query? first whenever a param type contains Query). Before this PR useAdminEntity always called fn(undefined, limit, offset); now it calls fn(limit, offset), so limit is sent as the query struct and offset as the limit — the admin list, relation picker and inverse relation tables all page wrongly. After merging with main the registry will say listHasQuery: true while this reads listQuery, so the regression survives the merge.

Suggested fix: rename to listHasQuery (matching main's generator and admin-types) and drop the branch-local listQuery from admin-types/index.ts.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in ed52ebd: pageArgs now reads config.listHasQuery, and admin-types/index.ts carries main's listHasQuery declaration (same doc comment and position) in place of the branch-local listQuery so the merge with main is clean.

}

function onKeydown(event: KeyboardEvent) {
if (!open.value && (event.key === 'ArrowDown' || event.key === 'Enter')) {

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Enter on a closed picker submits the enclosing form.

When the listbox is closed, Enter only sets open = true and returns without event.preventDefault(), so the same keypress also submits the surrounding edit/create form.

Scenario: focus the picker (opens), press Escape to dismiss the list, then press Enter to reopen it — the list flashes open and the form submits.

Suggested change
if (!open.value && (event.key === 'ArrowDown' || event.key === 'Enter')) {
if (!open.value && (event.key === 'ArrowDown' || event.key === 'Enter')) {
event.preventDefault()

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in ed52ebd: the closed-picker ArrowDown/Enter branch now calls event.preventDefault() before opening the list, so Enter no longer submits the enclosing form.

… Enter on a closed picker does not submit the form

The generator emits listHasQuery, not listQuery, so pageArgs never saw
the query-taking shape and sent limit as the query struct. The picker's
closed-state Enter now prevents default so it only reopens the list.
sksizer added a commit that referenced this pull request Sep 21, 2026
Eight specs authored in the consuming dev monorepo against ontogen's
source — six of them already in review here as #156, #158, #159, #160,
#161 and #166 — move into docs/planning/tasks/ so the reasoning behind
each change lives with the generator rather than with one consumer.

Rewritten upstream-native: repo-relative paths, the consumer's situation
as motivation rather than scope, and the consumer-side acceptance
criteria (pin bumps, drift gates, fallback plans) left behind in the
monorepo where they belong.

Two carry no implementation yet: the schema field-type vocabulary
(bytes, timestamp, date, JSON, defaults, declared indexes) and explicit
route shapes. The consumer's ninth item, per-variant HTTP error status,
is already covered by the E0003 epic and is not duplicated here.
Main independently extracted into helpers the same logic this branch wrote
inline, so each conflict is a choice between two spellings of one idea:

- AdminRelationTables: main's `fetchAllItems` replaces the branch's inline
  paging loop. It already handles `listHasQuery`, and adds the guards the
  loop lacked — a ceiling for a `total` that keeps moving under concurrent
  inserts, and a hard 1000-request cap.
- edit.vue: main's `toUpdateInput` replaces the branch's inline null
  coercion. Its `isBlank` also treats `null` as empty, which the branch's
  `'' || undefined` check missed.
- useAdminEntity: keeps the branch's `pageArgs` helper for the call shape
  (identical to main's inline ternary, and still used by the relation
  picker) and takes main's stale-response guard and page clamp-and-retry.
- package.json: the branch's deliberate 0.2.x feature bump over main's
  0.1.1.

The merge then combined the branch's auto-imported helpers with main's test
files, which stub globals per file and knew nothing about them, so
`pageArgs` and `useAdminDetailLink` were undefined under vitest. Both are
pure — a plain function, and an inject with a null-returning default — so
they join `ref`/`computed` in tests/setup.ts, which exists to stand in for
exactly these auto-imports.
@sksizer
sksizer merged commit ae909e4 into main Sep 23, 2026
1 of 2 checks passed
@sksizer
sksizer deleted the admin-layer-pickers branch September 23, 2026 06:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant