Skip to content

fix(blocks-engine): a reference is held to the walk's name rule, and refusals keep the type line - #1885

Merged
mobeenabdullah merged 2 commits into
mainfrom
fix/dtcg-reference-names-and-early-type-loss
Sep 13, 2026
Merged

mobeenabdullah merged 2 commits into
mainfrom
fix/dtcg-reference-names-and-early-type-loss

Conversation

@mobeenabdullah

@mobeenabdullah mobeenabdullah commented Sep 13, 2026

Copy link
Copy Markdown
Collaborator

What

Two defects in the DTCG token reader's import report. Both were found after #1867 merged, and both come from one question being answered in more than one place.

1. $extends: "{base.$private}" was called an inheritance

isReference judged each segment of a {group.path} reference by emptiness and the forbidden characters ., { and }. It never checked the $ prefix, which the walk reserves for the format's own keys and never reads as a group. So a reference running through $private or $root got the line "inherits from another group" when it names nothing.

  • isReservedKey(key): the one test for "this key is the format's own". read() uses it to route a group's $ keys to unreadGroupField, and unreadTokenParts uses it to tell a token's fields from its children.
  • isDtcgName(segment): a segment that is not blank, not reserved, and free of DTCG_NAME_FORBIDDEN. isReference now asks this for every segment, so a reference can't accept a name the walk would never read.

2. A refused token lost the line about its ignored $type

readToken returned early before reaching typeUnread in four cases: a name with a forbidden character, a name this site can't author, an unusable stated id, and a name over the cap. A non-string $type beside any of those went unreported, while the later refusals (no kind, unreadable value, unsafe value) did report it.

  • refuse(...reasons): every exit in readToken now goes through this one routine. It names the ignored type first, credits no group, then pushes the refusal.
  • Unsafe values: that refusal previously spliced the type line in by hand. It now collects isWritableValue's lines and passes them through refuse like every other exit.

Tests (engine dtcg.test.ts, 141 → 144)

  • calls $extends an inheritance only when it names something: adds {base.$private}, {$root} and {a.$b}, each expected to get the generic unread-field line. The existing {base} control still gets the inheritance line.
  • reads a key as a group name exactly when a reference may name it (new): an agreement test. For brand, $private, $root, a{b and a}b, it checks that the walk reads the key as a group exactly when {key} is reported as an inheritance. A control proves both answers occur.
  • is named ahead of every refusal, whichever check refused the token (new): six labelled rows, one per refusal (forbidden character, unauthorable name, unusable id, length cap, no kind, unreadable value). Each row first asserts its own refusal line is present, so it reached that exit. It then asserts the type line is present, comes before the refusal, and credits no group.

Verification

  • Red first: the three tests above failed on main's reader for the intended reasons.

  • Gates:

    • dtcg.test.ts 144/144; builder tokens-transfer.test.ts 58/58
    • pnpm check-types 44/44 from the root; lint 2/2
    • FALLOW_AUDIT_BASE=origin/main fallow audit: pass, 3 changed files, every *_introduced at 0
    • check-comment-convention clean; check-changesets covers the group
  • Mutants: every mutant is killed on the committed tree, each by the test named for its property, and the files are restored byte-identical after the run. The 11 new ones:

    • dropping the $ rule from isDtcgName;
    • giving isReference its own predicate again;
    • removing the type line from refuse;
    • moving the type line after the refusal;
    • seven mutants, each sending one exit around refuse (malformed name, unauthorable name, unusable id, naming cap, no kind, unreadable value, unsafe value).

    The earlier rounds' mutants whose target text this change removed were retargeted at the new code and are killed too.

Changeset

a-token-reference-names-only-a-group-the-reader-walks.md: all 26 lockstep packages, patch.

Summary by CodeRabbit

  • Bug Fixes

    • Improved design token validation for reserved $-prefixed names and references.
    • Corrected group-name detection when references contain reserved path segments.
    • Improved error reporting so unusable $type declarations are reported consistently alongside other validation errors.
  • Tests

    • Added coverage for malformed references, group-name recognition, and validation error ordering.

…refusals keep the type line

isReference accepted {base.$private}: it checked emptiness and the forbidden
characters, not the $ prefix the walk treats as reserved. Both now ask
isDtcgName, built from isReservedKey and DTCG_NAME_FORBIDDEN, which read() and
unreadTokenParts also use to tell a field from a name.

readToken returned early for a malformed or unauthorable name, an unusable
stated id and a naming cap before any typeUnread call, so an ignored $type
beside those went unsaid. Every refusal now goes through one routine that
names the ignored type first, crediting no group.
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 13, 2026

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review Completed 2026-09-13T19:50:56.416524Z d3850b7 Manual request
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@coderabbitai

coderabbitai Bot commented Sep 13, 2026

Copy link
Copy Markdown

Review Change StackReview Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Advanced

Run ID: 8bd82112-9cbf-4e79-bbd3-244532ad35c8

📥 Commits

Reviewing files that changed from the base of the PR and between 2510625 and d3850b7.

⛔ Files ignored due to path filters (1)
  • .changeset/a-token-reference-names-only-a-group-the-reader-walks.md is excluded by !.changeset/**
📒 Files selected for processing (2)
  • packages/blocks-engine/src/style/dtcg.test.ts
  • packages/blocks-engine/src/style/dtcg.ts

Included review availability: Your plan provides up to 1 included review per hour; 0 remain after this review.


📝 Walkthrough

Walkthrough

The DTCG walker now shares reserved-key and name validation across reads and references. Token refusals use a common path that reports unusable $type values before refusal reasons. Tests cover reserved reference segments and refusal ordering.

Changes

DTCG validation consistency

Layer / File(s) Summary
Reference and name validation
packages/blocks-engine/src/style/dtcg.ts, packages/blocks-engine/src/style/dtcg.test.ts
Adds shared validation for reserved keys and DTCG names. $extends references with reserved path segments no longer count as group references. Tests cover group-name detection and malformed references.
Type refusal ordering
packages/blocks-engine/src/style/dtcg.ts, packages/blocks-engine/src/style/dtcg.test.ts
Routes token refusal paths through refuse(...), which reports unusable $type before the refusal reason. Tests cover six refusal cases and verify zero tokens and issue ordering.

Priority: ⬇️ Low

Estimated code review effort: 3 (Moderate) | ~20 minutes

Change: Bug fix

Merge Risk: ⚪ Minimal · up to d3850

The DTCG import-report fixes are covered by targeted tests, with no current merge-blocking risk identified.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly identifies both main fixes: DTCG reference name handling and type reporting on refusal paths. It is specific and concise enough for project history.
Description check ✅ Passed The description is detailed and on-topic. It explains the defects, implementation, tests, verification, and changeset. It does not use all template headings or explicitly complete the type-of-change, …
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 7 functions across 2 files.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/dtcg-reference-names-and-early-type-loss

Warning

Some tools did not complete. Review the errors below.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

packages/blocks-engine/src/style/dtcg.test.ts

ESLint skipped: missing config or dependency (missing-dependency). The ESLint configuration references a package that is not available in the sandbox.

packages/blocks-engine/src/style/dtcg.ts

ESLint skipped: the matched ESLint configuration already failed (missing-dependency).


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@mobeenabdullah

Copy link
Copy Markdown
Collaborator Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 9436e9a63e

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +1645 to +1647
const walked = names({
[key]: { t: { $type: "number", $value: 1 } },
}).includes(`${key}.t`);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Make the group-walk assertion observe traversal

If read() stops routing $private or $root through isReservedKey() and descends into that object, this assertion still passes: names() remains empty because the downstream token-name validation rejects $private.t, while isReference() also returns false. The test therefore does not verify the claimed agreement between reference parsing and group traversal; assert a walk-specific diagnostic or the shared predicate so traversal itself is observable.

AGENTS.md reference: AGENTS.md:L216-L220

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Agreed. The walk was never observed: a later name check refuses $private.t whether or not read() routed the key aside, so names() came back empty either way. Fixed in d3850b7b1.

Each key is now judged by the walk's own line about it:

  • the reserved-field line ("$private" is a design-token field this site does not read) when the walk routes the key aside;
  • the malformed-name line ("a{b" is not a usable name in a design-token file) when it rejects the name.

For each row, the test asserts which of the two lines is present and that the other is absent. So brand passes because the walk wrote neither line, not because a lookup found nothing. It then asserts that {key} is reported as an inheritance exactly when the walk accepted the key.

Break-verified on the committed tree: routing $private as a name (if (isReservedKey(key) && key !== "$private")) now fails this test by name. Engine dtcg.test.ts 144/144; blocks-engine lint and check-types (44/44) pass; the comment check is clean; fallow passes on 3 changed files with nothing introduced.

@pkg-pr-new

pkg-pr-new Bot commented Sep 13, 2026

Copy link
Copy Markdown

Open in StackBlitz

@nextlyhq/adapter-drizzle

npm i https://pkg.pr.new/@nextlyhq/adapter-drizzle@9436e9a

@nextlyhq/adapter-mysql

npm i https://pkg.pr.new/@nextlyhq/adapter-mysql@9436e9a

@nextlyhq/adapter-postgres

npm i https://pkg.pr.new/@nextlyhq/adapter-postgres@9436e9a

@nextlyhq/adapter-sqlite

npm i https://pkg.pr.new/@nextlyhq/adapter-sqlite@9436e9a

@nextlyhq/admin

npm i https://pkg.pr.new/@nextlyhq/admin@9436e9a

@nextlyhq/admin-css

npm i https://pkg.pr.new/@nextlyhq/admin-css@9436e9a

@nextlyhq/blocks-engine

npm i https://pkg.pr.new/@nextlyhq/blocks-engine@9436e9a

@nextlyhq/blocks-react

npm i https://pkg.pr.new/@nextlyhq/blocks-react@9436e9a

@nextlyhq/builder

npm i https://pkg.pr.new/@nextlyhq/builder@9436e9a

create-nextly-app

npm i https://pkg.pr.new/create-nextly-app@9436e9a

@nextlyhq/eslint-plugin

npm i https://pkg.pr.new/@nextlyhq/eslint-plugin@9436e9a

nextly

npm i https://pkg.pr.new/nextly@9436e9a

@nextlyhq/plugin-form-builder

npm i https://pkg.pr.new/@nextlyhq/plugin-form-builder@9436e9a

@nextlyhq/plugin-mcp

npm i https://pkg.pr.new/@nextlyhq/plugin-mcp@9436e9a

@nextlyhq/plugin-page-builder

npm i https://pkg.pr.new/@nextlyhq/plugin-page-builder@9436e9a

@nextlyhq/plugin-sdk

npm i https://pkg.pr.new/@nextlyhq/plugin-sdk@9436e9a

@nextlyhq/plugin-seo

npm i https://pkg.pr.new/@nextlyhq/plugin-seo@9436e9a

@nextlyhq/storage-s3

npm i https://pkg.pr.new/@nextlyhq/storage-s3@9436e9a

@nextlyhq/storage-uploadthing

npm i https://pkg.pr.new/@nextlyhq/storage-uploadthing@9436e9a

@nextlyhq/storage-vercel-blob

npm i https://pkg.pr.new/@nextlyhq/storage-vercel-blob@9436e9a

@nextlyhq/ui

npm i https://pkg.pr.new/@nextlyhq/ui@9436e9a

commit: 9436e9a

@github-actions

github-actions Bot commented Sep 13, 2026

Copy link
Copy Markdown
Contributor

Whole-Repository Code Hygiene Summary

Full dead-code, duplication, and complexity report for the PR branch as it stands now. Playground is excluded. Quality gate enforcement on introduced issues is performed by the Changed files job.

🌿 Fallow

Warning

Review needed

⚠️ 73 code issues · ⚠️ 677 clone groups · ⚠️ 1036 health findings

See inline review comments for per-finding details.

Code issues (73)
Category Count
Unused files 2
Unused exports 5
Unused dependencies 19
Unused devDependencies 6
Unresolved imports 2
Unlisted dependencies 1
Circular dependencies 38
Duplication (677 groups · 28396 lines · 3.9%)
Locations Lines Tokens
schemas/_dialect-bundles/mysql.relations.ts:40-134
schemas/_dialect-bundles/postgres.relations.ts:40-134
schemas/_dialect-bundles/sqlite.relations.ts:40-134
95 593
cli/commands/db-sync-demote.ts:70-75
cli/commands/db-sync-promote.ts:38-43
cli/commands/dev-build.ts:100-105
cli/commands/dev-build.ts:179-184
cli/commands/dev-build.ts:299-304
cli/commands/dev-build.ts:411-416
cli/commands/dev-build.ts:552-557
cli/commands/dev-server.ts:575-580
cli/commands/dev-server.ts:840-845
cli/commands/dev-server.ts:1143-1148
cli/commands/migrate-field-groups.ts:110-115
6 70
entries/EntryList/EntryTableSkeleton.tsx:74-98
collection/components/CollectionTableSkeleton.tsx:94-118
field-group/components/FieldGroupTableSkeleton.tsx:90-114
plugins/components/PluginsTableSkeleton.tsx:86-110
singles/components/SinglesTableSkeleton.tsx:77-101
src/components/table-skeleton.tsx:100-124
25 89
collections/config/validate-config.ts:380-433
field-groups/config/validate-field-group.ts:185-238
singles/config/validate-single.ts:190-243
54 152
dispatcher/handlers/collection-dispatcher.ts:925-967
field-groups/services/field-group-table-provisioning.ts:186-236
singles/services/reconcile-single-companion.ts:110-160
51 149

… and 672 more groups.

Across 425 files.

Complexity (1036 functions above threshold)
File Function Severity Cyclomatic Cognitive CRAP Lines
singles/services/single-mutation-service.ts:966 <arrow> critical 246 ! 308 ! 13317.5 ! 1650
collections/services/collection-mutation-service.ts:6366 <arrow> critical 168 ! 155 ! 6264.4 ! 1307
src/init/reload-config.ts:1417 applyReload critical 143 ! 211 ! 4560 ! 1470
shared/lib/entry-validation.ts:245 validateFieldValue critical 109 ! 157 ! 2675.3 ! 432
dynamic-collections/services/dynamic-collection-schema-service.ts:1050 generateAlterTableMigration critical 104 ! 221 ! 2440.3 ! 782

5134 files, 80066 functions analyzed (thresholds: cyclomatic > 20, cognitive > 15, CRAP >= 30)

Codebase health

Metric Value
Maintainability 91.7 / 100
Avg complexity 1.8

Tip

Run fallow fix --dry-run to preview auto-fixes.
Add /** @public */ above exports to preserve them.

… about each key

The agreement test called a key read as a group when its token name came
back, and a later name check refuses $private.t whether or not read() routed
the key aside, so the walk itself was never observed. Each key is now judged
by the walk's reserved-field or malformed-name line, each asserted present.
@mobeenabdullah

Copy link
Copy Markdown
Collaborator Author

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Breezy!

Reviewed commit: d3850b7b1c

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@mobeenabdullah
mobeenabdullah merged commit 0708ec6 into main Sep 13, 2026
12 checks passed
@github-actions github-actions Bot added the type: docs Documentation only label Sep 13, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

type: docs Documentation only

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant