Skip to content

fix(clients): keep the admin registry's indentation, and regenerate the examples - #179

Open
sksizer wants to merge 2 commits into
mainfrom
fix/iron-log-admin-registry-fields
Open

sksizer wants to merge 2 commits into
mainfrom
fix/iron-log-admin-registry-fields

Conversation

@sksizer

@sksizer sksizer commented Sep 26, 2026

Copy link
Copy Markdown
Owner

Brings the examples' checked-in generator output back in line with what the pipeline actually emits, and fixes a generator bug found while doing it.

Two commits, separable.

1. The generator bug

Inside a Rust string literal, a trailing \ eats the newline and every leading space on the next line. The admin-registry template used \ continuations to splice in two optional chunks, so it was silently dropping the indentation on the two lines that followed them:

export const adminEntities: AdminEntityConfig[] = [
{                            // wanted two spaces
    key: 'exercise',
    paginated: false,
fields: [                    // wanted four
      { key: 'id', ... }],
  },

The optional chunks already carry their own trailing newline, so they splice inline and the continuations were never needed. Dropping them restores the intended layout, with a comment recording why \ must not come back.

Output only — no field, flag or value changes. The registry tests assert on content rather than layout.

2. The regeneration

The examples commit their generated trees so diffs stay reviewable, but nothing regenerates them. They had drifted badly — iron-log's lockfile was still pinning ontogen 0.3.1.

What the rebuild pulls in, none of it hand-authored:

Change Origin
count_* store methods + PaginatorTrait import pagination pushdown in #159, never written back
Admin-registry indentation the fix above
Semicolons on emitted TypeScript an earlier generator change the committed files predate
sha2 dependency chain in notes-kb / tasks-tracker transitive, via lockfile refresh
Version bumps across every lockfile 0.3.1 → 0.7.1 and friends

All four examples (iron-log, iron-log-md, notes-kb, tasks-tracker) build against the regenerated output.

Doing this now means the next generator change produces a diff that is only that change, rather than one carrying two releases of backlog.

A correction to what I reported on #178

On #178 I flagged that regenerating iron-log appeared to strip the fields: metadata from admin-registry.ts, and guessed at a ClientsConfig::schema_entities forwarding bug.

That was wrong. The metadata is intact — 26 field keys and 4 fields: arrays, before and after. I had read the first 20 lines of a diff whose leading change was the indentation shift above, which re-diffed the whole file; the - lines I saw were re-added below my window. Pipeline does forward schema.entities correctly (src/pipeline.rs:583-587).

The real change there is formatting: field objects went from multi-line blocks to one per line, 254 → 109 lines. No data lost.

Verification

just full-check green — fmt, clippy, the full Rust workspace, and 43 vitest tests. Plus cargo check on each of the four examples.

Inside a Rust string literal a trailing `\` eats the newline *and every
leading space on the next line*. The registry template used `\`
continuations to splice in two optional chunks, so it was silently
losing the indentation on the two lines that followed them: every
entity's opening brace and its `fields:` key both landed in column 0
while their contents stayed indented.

    export const adminEntities: AdminEntityConfig[] = [
    {                          <- wanted two spaces
        key: 'exercise',
        paginated: false,
    fields: [                  <- wanted four
          { key: 'id', ... }],
      },

The optional chunks already carry their own trailing newline, so they
splice inline and the continuations are not needed at all. Dropping them
restores the indentation the template always intended, and a comment
records why `\` must not come back.

Output only; no field, flag or value changes. The registry tests assert
on content rather than layout and are unaffected.
The examples commit their generated trees so diffs are reviewable, but
nothing regenerates them, so they had drifted a long way from what the
pipeline actually emits. iron-log's lockfile was still pinning ontogen
0.3.1.

What the rebuild brings in, none of it authored by hand:

- `count_*` store methods and the `PaginatorTrait` import they need,
  from the pagination pushdown in #159 — never written back at the time.
- The admin registry's indentation, from the fix in the previous commit.
- Semicolons on the emitted TypeScript, from an earlier generator
  change; the committed files predate it.
- A `sha2` dependency chain in notes-kb and tasks-tracker, and version
  bumps across every lockfile.

All four examples build against the regenerated output. Doing this now
means the next generator change shows a diff that is only that change,
instead of one carrying two releases of backlog.
@cloudflare-workers-and-pages

Copy link
Copy Markdown

Deploying ontogen with  Cloudflare Pages  Cloudflare Pages

Latest commit: f301590
Status: ✅  Deploy successful!
Preview URL: https://0b46a1d4.ontogen.pages.dev
Branch Preview URL: https://fix-iron-log-admin-registry.ontogen.pages.dev

View logs

},
{ key: 'notes', label: 'Notes', type: 'string', showInDetail: true, showInForm: true },
],
{ key: 'id', label: 'Id', type: 'string', required: true, showInTable: true, showInDetail: true, showInForm: true, isId: true },

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.

These regenerated TS files break iron-log's own Prettier config. src-nuxt/.prettierrc sets semi: false and printWidth: 100, and .prettierignore doesn't exclude app/generated/ or app/admin/generated/. The previous committed files were Prettier-formatted (no semicolons, wrapped field objects), so the ; and >100-col lines here are the unformatted generator output — not "an earlier generator change the committed files predate" as the PR description says.

Failure: pnpm format:check / just ci fails, and the lefthook prettier --check {staged_files} pre-commit hook rejects any commit that stages these files. Running pnpm format then cargo build flips them back and forth forever. The same applies to app/generated/transport.ts and app/generated/types.ts.

WorkoutSet,
WorkoutStats,
} from './types'
} from './types';

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.

Root cause is in examples/iron-log/src-tauri/build.rs, not the committed output. iron-log's ClientsConfig falls through to ts_formatter: TsFormatter::None, so build.rs writes unformatted TS into a tree that Prettier owns. Committing the raw output only moves the drift around. Set ts_formatter: TsFormatter::Command(vec!["prettier".into(), "--stdin-filepath".into()]) in iron-log's ClientsConfig, or add the two generated/ dirs to src-nuxt/.prettierignore. Then the checked-in files are stable no matter who regenerates them.

},
],
{ key: 'tags', label: 'Tags', type: 'relation-array', relationTo: 'tag', showInTable: true, showInDetail: true, showInForm: true },
{ key: 'created_at', label: 'Created At', type: 'string', required: true, showInDetail: true, showInForm: true }],

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.

This is a real behaviour change, not formatting. Workout created_at changed from readOnly: true (no showInForm) to showInForm: true with no readOnly, here and in iron-log-md. So the PR description's claims ("no field, flag or value changes", "No data lost", "The real change there is formatting") are wrong for this file.

Failure: the admin UI now shows created_at as an editable form input on both create and edit. A user can rewrite a workout's creation timestamp from the edit form. This comes from the earlier is_read_only = ... (key == "created_at" && !is_required) generator change. Either it's intended, and should be called out and reviewed as such, or created_at should be read-only on update.

[[package]]
name = "ontogen"
version = "0.3.1"
version = "0.7.1"

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.

The drift will come back. examples-ci.yml already runs cargo build / cargo check on each example, and that runs build.rs and regenerates the committed trees in place. But CI never checks that the output matches what's committed. That's how iron-log ended up pinning ontogen 0.3.1 through several releases. Add a git diff --exit-code -- examples/ step after each build (and --locked on the cargo invocation). Then a stale generated tree or lockfile fails the PR, and nobody has to find it and hand-regenerate later.

{pagination_js}\
{list_has_query_js}\
fields: [{fields_js}],
{pagination_js}{list_has_query_js} fields: [{fields_js}],

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.

No regression test pins this fix. The PR notes that the registry tests assert on content, not layout. So putting a \ continuation back in this template (the exact bug fixed here) would still pass just full-check. A small assertion in src/clients/tests.rs on the emitted "\n {\n key: '" and "\n fields: [" substrings, or an insta snapshot of admin::generate output, would lock in the indentation.

" {{
key: '{module}',
plural: '{plural}',
label: '{label}',

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.

label / pluralLabel (and key/plural) are interpolated without ts_escape, even though field labels a few lines below go through ts_escape. These values can come from NamingConfig.label_overrides / plural_label_overrides, which are user-supplied. Failure: label_overrides: {"author": "Author's Pick"} emits label: 'Author's Pick',, which is a TS syntax error in the generated registry. Wrap them in ts_escape(...) the same way generate_fields_for_entity does.

{
key: 'workout_set',
plural: 'workout_sets',
label: 'Workout_set',

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.

Module labels for multi-word modules come out as 'Workout_set' / 'Workout_sets'. NamingConfig::label (src/servers/types.rs:707) uses capitalize(module), which keeps the underscore. That text shows up in the admin UI. admin.rs already has field_label(), which gives "Workout Set". Reuse that, or a shared snake-to-title helper, as the default module label.

name?: string | null;
muscle_group?: string | null;
equipment?: string | null;
notes?: string | null | null;

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.

string | null | null: the regenerated Update DTO types show a doubled | null on every optional-nullable field (notes, name, duration_minutes, …). It type-checks, but it's a TS-bindings generator bug for Option<Option<T>> update fields, where the inner Option and the patch Option each add | null. This PR now commits it into the reviewed baseline. Worth deduping in the emitter so this baseline doesn't have to churn later.

{ key: 'name', label: 'Name', type: 'string', required: true, showInTable: true, showInDetail: true, showInForm: true },
{ key: 'muscle_group', label: 'Muscle Group', type: 'string', required: true, showInDetail: true, showInForm: true },
{ key: 'equipment', label: 'Equipment', type: 'string', required: true, showInDetail: true, showInForm: true },
{ key: 'notes', label: 'Notes', type: 'string', showInDetail: true, showInForm: true }],

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.

The layout is still not what the template comment calls "intended". The closing ] of fields: hugs the last element (... }],), because generate_fields_for_entity (admin.rs:219) prepends \n to each item and never emits a newline before ]. Its trailing-comma check also uses the unfiltered entity.fields.len(), so it's wrong when the last field is FieldRole::Skip. If the goal is output that is stable for review, emit fields: [\n{items joined by ",\n"},\n ],, filtering first and then joining.

Ok(notes)
}

pub async fn count_notes(&self) -> Result<u64, AppError> {

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.

count_notes can disagree with list_notes. count_notes counts every path from list_paths, while list_notes goes through read_all, which continues past any path whose file stem isn't valid UTF-8. Failure: a vault containing a record with a non-UTF-8 filename reports count = N+1 while listing returns N rows, so pagination totals are off. Count list_ids(NOTES_DIR) instead, which applies the same stem filter.

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