Skip to content

docs: every docs code sample parses with @objectstack/spec 17.7.0, held by a CI gate (closes #316) - #321

Merged
hotlong merged 28 commits into
mainfrom
claude/pm-dispatch-objectos-ju9td1
Oct 7, 2026
Merged

hotlong merged 28 commits into
mainfrom
claude/pm-dispatch-objectos-ju9td1

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Fixes #316

This is the second of two stacked PRs that close the docs code-sample family (#301, #307, #316). It is based on PR A (staged/issue-316-a), and its diff here is against A. It does two things:

  • The samples on the remaining 14 English pages parse with @objectstack/spec 17.7.0 (npm latest; versioned in objectstack 4e4e8814).
  • A CI gate keeps every ts/js block on every English page parsing against a pinned spec.

With A and #315 (already on main), every English code sample parses.

Size: 4280 changed lines against A (+1100/−3180). check-governed-merges --test on this PR's 38 paths, with that size, answers "NOT governed … 4280 changed line(s) ≤ 5000". Each English correction ships here with its own sibling deletions, as ruling A requires.

Two commits on A's tip e585f02: 9144f75 (the pages) and a45e682 (the gate). This branch's tree equals the reviewed work merged onto main: git merge-tree --write-tree dc8213d 853ed96 gives 80a304d, and so does staged/issue-316-b^{tree}, so git diff dc8213d staged/issue-316-b is byte-identical to that merge's diff.

What changed, per page

Sources are the published spec, packages/spec/src/** at objectstack 4e4e8814. The npm tarball ships the same files. ObjectStack's public docs at the same commit are cited as "ObjectStack content/docs/…".

  • reference/cel.mdx:
    • The six one-liners become real slots: a formula field; a script rule's condition, which is TRUE for the invalid record; a field's visibleWhen; a start node's condition; a schedule's expression: cron…; and a notify node's plain-string title.
    • The "Where each one is used" table names keys that exist. Field.conditionalRequired becomes requiredWhen: the alias was removed in protocol 17 (data/field.zod.ts:1817). Validation.predicate becomes a script rule's condition. Flow.step.when becomes a start node's or an edge's condition. Action.guard becomes visible / disabled.
    • tmpl for notification subjects/bodies is reversed. Measured, NotifyConfigSchema refuses a tmpl value for title ("expected string"). The typed template slots are the deprecated titleFormat (data/object.zod.ts:2239) and the AI prompt templates (ai/model-registry.zod.ts:121).
    • The { dialect, source } type signature is opted out of the gate and renders as before.
  • build/agents.mdx:
    • knowledge was removed in 17.0.0 (ai/agent.zod.ts:429). Grounding now goes in instructions, and source access is set per source.
    • memory.shortTerm (:479) and longTerm.store (:196) were removed. The memory table now documents longTerm.enabled, longTerm.maxEntries and reflectionInterval.
    • "Any manual flow (type: 'manual')" becomes a type: 'flow' action (ObjectStack ai/actions-as-tools.mdx).
  • configure/data-sources.mdx (it has no locale siblings: docs: delete the 14 locale siblings that still forecast federation as unshipped #290 deleted them):
    • config.connection is not a key; a postgres config is flat.
    • An inline password is refused, even one read from the environment. The secret is named in external.credentialsRef (data/datasource.zod.ts:318, prescription in data/driver/common.zod.ts:106).
    • The sample uses defineDatasource(). The claim that a plain object "is exactly what the examples/app-crm stack does" is reversed: at 4e4e8814 that stack uses defineDatasource.
    • The manifest gets type and name, and AnalyticsReplica is declared.
  • build/interface/apps.mdx: mobileNavigation was removed in 17.0.0, "fully unimplemented" (ui/app.zod.ts:1649).
  • build/interface/dashboards.mdx: refreshIntervalSeconds (ui/dashboard.zod.ts:1775); no certified measures, a key that does not exist.
  • quickstart.mdx: Field.lookup('sys_user', …). configure/permissions/record-access.mdx, reference/rest-api.mdx: the objects get their required fields.
  • build/interface/forms.mdx, build/interface/pages.mdx, configure/permissions/{field-level-security,permission-sets}.mdx, reference/field-types.mdx: fragment comments, real options, and conditionalRequired → requiredWhen in the field-types table.
  • reference/objectql.mdx: the query-shape type signature is opted out and renders as before.

Locale siblings: ruling A of #256 (AGENTS.md, #319)

Page Call Siblings
reference/cel delete: tmpl for notifications is reversed; the table's keys are removed or renamed de, es, fr, ja, ko, zh-Hans, zh-Hant
build/agents delete: the knowledge block, the memory tiers and the manual flow are removed de, es, fr, ja, ko, zh-Hans, zh-Hant
build/interface/apps delete: mobile navigation is removed zh-Hans, zh-Hant
build/interface/dashboards delete: certified measures are removed zh-Hans, zh-Hant
forms, pages, record-access, rest-api, field-level-security, permission-sets, field-types, objectql keep: code shape only stale, reported
data-sources, quickstart nothing to delete none exist

18 files are deleted. Before each deletion, I confirmed that the sibling carries the claim. gen-zh-hant --check passes with 54 files.

The gate: .github/scripts/check-doc-samples.mjs

The script header has the full contract.

  • What it reads: every English page (the locales come from apps/docs/lib/i18n.ts), and in it every fenced ts/js block.

  • How it judges a block, by the first rule that matches:

    1. the opt-out marker;
    2. not a spec sample (N/A): it imports only from other packages, or it constructs a class one of them provided;
    3. CEL strings, checked for slot shape only, because the spec ships no CEL parser;
    4. a fragment, whose first comment names its kind (22 kinds; docs: make the views and flows code samples parse with @objectstack/spec 17.7.0 #315's own comments match unchanged);
    5. statements, evaluated as published with only TypeScript syntax removed.

    Every spec constructor call is a parse. Imports are checked against the entry point, and type imports go through the TypeScript compiler. Every flow is also parsed against its executor contracts, and a stack's requires is judged with its page's flows, as Docs code samples the current @objectstack/spec refuses: the remaining view fragments in build/interface/views.mdx and the send_email flow action in build/automation/flows.mdx #307 did. A block that matches no rule fails.

  • The opt-out marker is {/* doc-sample: skip — WHY */}, on the last line before the fence. It is an MDX comment: it renders nothing, and lib/source.ts strips it from the llms bodies. Use it for a counter-example or a type signature. A marker without a reason, a marker that drifted off its fence, and a marker on an unread block all fail. Two blocks use it, both type signatures.

  • The pin: .github/scripts/doc-samples/package.json pins @objectstack/spec to exactly 17.7.0, with an npm lockfile. The gate refuses any other installed version.

    • It sits outside the pnpm workspace on purpose. As a devDependency of tools/ci-scripts, it re-resolved fumadocs-core's optional zod peer from 4.4.3 to 4.6.5 while fumadocs-mdx kept 4.4.3. That is two zod copies in the docs build.
    • The workspace lockfile is untouched.
  • Bumping it: a Dependabot entry for /.github/scripts/doc-samples, in the objectstack group, opens the bump as a PR. The gate runs on that PR, so a release that newly refuses a sample turns its own bump red. By hand, run npm install --prefix .github/scripts/doc-samples --save-exact @objectstack/spec@VERSION.

  • CI: npm ci --prefix .github/scripts/doc-samples, then the gate, next to "Generated zh-Hant is current", before type-check. The --self-test joins tools/ci-scripts/run-self-tests.mjs.

    • A spec bump moves the test task's hash. Measured with the lockfile mutated and then restored: a36d68f → bd1b30c → a36d68f.
  • Self-test: 51 cases. Each of the 22 fragment kinds must have a fixture it passes and a fixture it refuses.

  • Cost: the gate takes about 2 s and the self-test about 6 s; npm ci installs 3 packages.

Parse results: @objectstack/spec 17.7.0

Page A (= dc8213d here) as published fragments classified this PR
build/agents.mdx 1 pass, 1 refused 1 pass, 1 refused 2 pass
reference/cel.mdx 1 pass, 7 unchecked 3 pass, 4 refused, 1 skip 7 pass, 1 skip
build/interface/dashboards.mdx 0 pass, 1 refused, 6 unchecked 5 pass, 2 refused 7 pass
build/interface/apps.mdx 1 pass, 3 unchecked 3 pass, 1 refused 3 pass
configure/data-sources.mdx 0 pass, 4 refused 0 pass, 4 refused 4 pass
build/interface/forms.mdx 1 pass, 3 unchecked 3 pass, 1 refused 4 pass
reference/rest-api.mdx 0 pass, 1 refused 0 pass, 1 refused 1 pass
reference/field-types.mdx 0 pass, 4 unchecked 4 pass 4 pass
configure/permissions/record-access.mdx 0 pass, 1 refused 0 pass, 1 refused 1 pass
build/interface/pages.mdx 0 pass, 4 unchecked 4 pass 4 pass
quickstart.mdx 0 pass, 1 refused 0 pass, 1 refused 1 pass
configure/permissions/field-level-security.mdx 0 pass, 1 unchecked 1 pass 1 pass
configure/permissions/permission-sets.mdx 0 pass, 1 unchecked 1 pass 1 pass
reference/objectql.mdx 0 pass, 1 unchecked 1 skip 1 skip
These 14 pages 4 pass, 9 refused, 30 unchecked 25 pass, 16 refused, 2 skip 40 pass, 2 skip

"Unchecked" means a fragment with no comment saying what it is; the gate fails it. The middle column adds only those comments.

Red and green:

Tree Verdict
dc8213d EXIT 1 · ✗ doc samples: 66 block(s) refused or unchecked on 21 page(s) · 35 pass · 10 not a spec sample · 0 opted out
A (e585f02) EXIT 1 · ✗ doc samples: 39 block(s) refused or unchecked on 14 page(s) · 62 pass, exactly this PR's 14 pages
this PR (a45e682) EXIT 0 · ✓ doc samples: every ts/js block on 79 English page(s) parses with @objectstack/spec 17.7.0 · 98 pass · 10 not a spec sample · 2 opted out
--self-test EXIT 0 · ✓ self-test: 51 case(s) hold, and each of the 22 fragment kinds has a fixture it passes and one it refuses (@objectstack/spec 17.7.0)

Page-text ablation, on a scratch copy of the merged tree:

  • retention: { maxAge: '14d' } → '14d' (anchor 1 → 0): EXIT 1, lifecycle.retention: Invalid input: expected object, received string.
  • Deleting the objectql marker (1 → 0): EXIT 1, "no fragment kind this gate knows".

Verification at a45e682

Gate Verdict
pnpm install --frozen-lockfile EXIT 0, and the workspace lockfile is unchanged
pnpm turbo run type-check --continue --force (lock) VERDICT command-exit 0 · ✓ Types generated successfully · 1 successful, 0 cached
NEXT_PRIVATE_STANDALONE=true pnpm turbo run build --force (lock) VERDICT command-exit 0 · 1 successful, 0 cached. The OG-image font-fetch errors are this container's egress, and they are non-fatal
pnpm turbo run test --force (lock) VERDICT command-exit 0 · ✓ 11 self-test(s) passed
check-doc-samples.mjs / --self-test EXIT 0 / EXIT 0 (above)
check-locale-surface.mjs EXIT 0 · ✓ every advertised URL has a source file and every source file is advertised; … neither llms consumer carries … an MDX comment
check-positioning.mjs EXIT 0 · ✓ positioning: 4 copies equal their constants; the brand is right in 659 pages and 2 llms bodies; no stale sentence in 79 English sources and the en entry of 2 legal pages
check-search-locales.mjs EXIT 0 · ✓ search locales: all 8 locales answer 200, find "permissions", find every own page by its title within the first 3 pages, and find nothing for a nonce
gen-zh-hant.mjs --check EXIT 0 · ✓ zh-Hant: 54 generated file(s) match the zh-Hans sources byte for byte.
Ownership, with the workflow argv against this PR's base (git diff --name-status --no-renames staged/issue-316-a...HEAD), --actor hotlong, TRANSLATION_BOT_LOGIN set EXIT 0 · ✓ 38 file(s) changed: 18 translation artifact(s) deleted, none added or modified. With the bot as actor (the control): EXIT 1. Against origin/main...HEAD: EXIT 0 · 64 files, 36 deleted; the control is EXIT 1
check-translations.mjs EXIT 0 · ✓ translations gate passed. Reported, not blocking: zh-Hans 39 stale / 39 missing; the others 20 stale / 59 missing
check-translation-output.mjs --self-test / --files EXIT 0 / EXIT 0 · ✓ translation output gate passed (213 pre-existing finding(s) reported)
check-node-floor.mjs --self-test / run EXIT 0 · ✓ self-test: 17 rule case(s), 24 satisfies case(s) and 18 range case(s) … / EXIT 0 · ✅ Every declared floor clears what the dependency tree requires, and the declarations agree.
check-half-states.mjs --self-test EXIT 0 · 1551 cases pass
Control bytes in the changed files 0

Browser. These pages are byte-identical to the reviewed work, so its measurements still apply. next start served the build, and Chromium loaded the changed pages at 1440 and 390:

  • HTTP 200 and the right h1 on every page;
  • scrollWidth equal to the viewport;
  • every code box inside the viewport: 303–1137 px at 1440, 17–373 px at 390;
  • no marker text visible;
  • /zh-Hant/docs/reference/cel answers 200 with the English body.

I looked at the CEL examples and table, the agent memory table, the apps mobile note, the data-sources declaration and the objectql type signature.

NOT MEASURED: behaviour on a running ObjectOS runtime.

Acceptance notes

  • A spec-internal mismatch, for objectstack. packages/spec/src/shared/expression.zod.ts's dialect table lists the template dialect for "notification subjects/bodies". But NotifyConfigSchema.title is z.string(), and it refuses a tmpl value: defineFlow and the notify contract both answer "expected string, received object". The seam runs from the spec's template-dialect row to automation/io-node-config.zod.ts NotifyConfigSchema.title. cel.mdx follows the parse. The seat routes this to objectstack.
  • Prose that still names things the spec lacks. reference/cel.mdx's variable table says input is available in "flow steps".
  • Node floor. check-node-floor reads pnpm-lock.yaml only, so the spec's engines.node >=22.0.0 in the npm lockfile is outside it. That is below the repo floor (22.12.0), so nothing is missed today.
  • AGENTS.md says the Chinese coverage is "62 of 79 pages". It is lower after A and B: number drift.

维护者速读(草稿)

Pushed head

a8ce2ea on claude/pm-dispatch-objectos-ju9td1, a fast-forward with no force. It starts at the remote branch head c57caa2 (PR #320), merges origin/main @ 5483c4a (#320's squash; after the merge the diff against main is empty), and cherry-picks 9144f75 and a45e682. git diff origin/main a8ce2ea is byte-identical to git diff e585f02 a45e682. Size: 38 files, +1100/−3180 = 4280 changed lines, under the 5000-line human-merge line.


Generated by Claude Code

objectstack-fleet Bot and others added 28 commits October 6, 2026 10:36
…ds or fewer

Adds a `seoTitle:` frontmatter line to every English page whose built
`<title>` read as one or two words plus the ` | ObjectOS` suffix, so the
tab/result title carries the terms a reader searches for while the H1,
sidebar and breadcrumb keep the short noun (#166's mechanism).

- 67 pages: each gains exactly one line; no `title`, `description` or
  body line changes.
- Every title leads with the page's own specific term, lifted from its
  description and headings, and renders at 52–60 characters including the
  suffix (measured on the built HTML).
- The three duplicated titles (Approvals, Dashboards, Notifications —
  each used by a build/configure page and a use page) are now distinct.
- English only. Locale siblings are translation artifacts that a
  non-translator commit may not modify (AGENTS.md, Translation workflow;
  check-translation-ownership.mjs); the translation pass carries the new
  key over, and the output report lists the 163 siblings now missing it.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FeA1nwBz1ohH65dvffUGKr
…AQ headings, title-weighted per-locale search, consistency pass

- Light-mode muted foreground to hsl(0 0% 40%); code comments recoloured in
  both shiki themes.
- Tables get an always-drawn scrollbar and a scroll-driven trailing fade.
- FAQ and License FAQ questions become headings.
- /api/search builds one locale's index on that locale's first search and
  weights title > heading > text; check-search-locales gains own-title-buried;
  smoke-docs asks /api/search in every locale with a nonce control.
- Consistency: one data-residency table, one license-validation sentence,
  ObjectSchema.create, "license" spelling, Configure title, glossary order plus
  AI seat and Position, logo to the docs home, a translated llms.txt example,
  three unsourced configure/ai claims removed (with their locale siblings),
  release-following lines pointed at a populated feed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FeA1nwBz1ohH65dvffUGKr
…comments

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FeA1nwBz1ohH65dvffUGKr
…ix apiMethods primitives

The Views page declared views inside a defineObject call under an
object-level `view` key. ObjectStack provides no defineObject, and
ObjectSchema.create rejects `view` as an unknown key. Its views are a
defineView container ({ list, listViews, form, formViews }, each view
bound through `data`) registered on the stack with `views: [...]`, which
both samples now show; both parse with @objectstack/spec 17.6.0.

The REST API page's allowed apiMethods values drop the eight retired ones
the spec strips at parse.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FeA1nwBz1ohH65dvffUGKr
…ype-check

`next typegen` loads next.config.mjs, and fumadocs-mdx 15.0.7's createMDX()
starts init() without awaiting it (dist/next/index.js:14-20). init rewrites
every .source/*.ts with fs.writeFile (dist/core-DlDe_Eze.js:232-236), which
truncates first. typegen ends in process.exit(0), so it can exit inside that
window and leave .source/server.ts empty for tsc: TS2306, CI run 37478051451.

Running the fumadocs-mdx CLI after typegen makes the CLI, which awaits its
writes, the last writer before tsc. typegen does not read .source.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FeA1nwBz1ohH65dvffUGKr
…ries, zh-Hant 404, Open-in links follow the page shown

- patches/fumadocs-ui@16.8.12.patch: the nine accessible names fumadocs-ui
  16.8.12 hard-codes in English (Open Search, Toggle Theme, Open/Collapse
  Sidebar, Copy Anchor Link, Copy/Copied Text, Toggle Menu, and Radix's
  "Main") read from its i18n context with the old literal as default. Eight
  keys are a backport of upstream 16.9.0's own names; lib/ui-text supplies
  all nine through RootProvider in every locale.
- app/[lang]/docs/layout.tsx: page-tree entries (sidebar, breadcrumb,
  prev/next footer) for pages a locale has no translation of carry
  lang="en", using the docs page's own translatedLocales detection.
- app/not-found.tsx: the 404 copy moves into lib/ui-text (notFound), so the
  zh-Hant string is generated from zh-Hans by gen-zh-hant and checked by
  --check like every other Traditional string.
- Open in ChatGPT / Claude on a translated page sends the assistant to the
  translated page itself; English pages and fallbacks keep the English .mdx.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FeA1nwBz1ohH65dvffUGKr
…pec 17.7.0

views.mdx: every list-view fragment carries its required top-level
columns, fontWeight is a string, the list view types table states the
required keys the spec declares, and each fragment says what it omits.

flows.mdx: every sample is rewritten from the old trigger/steps shape to
FlowSchema's nodes and edges. Email goes through a notify node with
channels: ['email']; requires lists automation and triggers.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FeA1nwBz1ohH65dvffUGKr
…ative GitHub icon, code blocks off the landmark list, a localized Open-in prompt

axe 4.14.0 reported four failures on every docs page in every locale,
English included (#308):

- landmark-one-main: the docs layout has no main landmark. DocsPage passes
  its other props to the article it renders, so the docs page sets
  role="main" there; no patch needed.
- label-content-name-mismatch: the language switcher shows the current
  language but was named "Choose a language" only. It is now named
  "English — Choose a language", "Deutsch — Sprache wählen", visible text
  first, both halves from the i18n context.
- svg-img-alt: the GitHub icon was an svg role="img" with no name inside a
  link already named "GitHub"; it is aria-hidden now.
- landmark-unique: every code block's scroll viewport was an unnamed
  role="region". The role goes; tabIndex 0 stays for keyboard scrolling.

The last three are hunks added to patches/fumadocs-ui@16.8.12.patch; its
header says why each is a hunk and not a slot, and what an upgrade must
re-check. None of the three is fixed upstream as of 16.16.2.

The "Open in ChatGPT / Claude" prompt sentence was English in every locale.
It is now the ui-text key pageActionsOpenInLLMPrompt, upstream 16.9.0's key
name, placeholder and English, translated in six locales and generated for
zh-Hant. ui-text's table() now also holds every locale's placeholders to
English, so a translation that drops {url} fails the build.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FeA1nwBz1ohH65dvffUGKr
…ree entries

#298 and #305 made two promises about every localized docs page that no
gate checked afterwards: no control is named in English, and every
page-tree entry for an untranslated page carries lang="en". The locale
surface gate now reads them off the prerendered HTML under
apps/docs/.next/server/app/<locale>/, script bodies (the RSC payload)
skipped:

- english-chrome-name: an accessible name (aria-label, title, alt,
  placeholder, svg title, .sr-only text) on a localized page that is one of
  the English pages' names, outside lang="en". The English set is read off
  the built English pages, minus GitHub and www.objectos.ai.
- untranslated-entry-unmarked: a sidebar item or previous/next card for a
  page the locale has no source file for, whose text does not resolve to
  lang="en". The oracle is the content tree, not the app's detection.
- translated-entry-marked-english: the over-marking direction.

Guards keep it from passing over nothing (a page missing from a locale's
build, no English name to compare, no entry of a kind recognised in a
locale), and a live control feeds both #305 shapes, built from the run's
real oracle values, through the same reader on every run. Ten self-test
cases, and the control shown red with a reader blinded to names or to lang.

On the HTML main built at cec227a it reports 6239 English names and 5974
unmarked entries; on #305's tree, 0 and 0. No ci.yml change: the existing
Locale surface step runs the gate after the build, and pnpm turbo run test
runs its self-test.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FeA1nwBz1ohH65dvffUGKr
Below 1280px fumadocs-ui 16.8.12 shows the page's table of contents as a
bar rendered in a <header> outside every sectioning element, so the page
had two banner landmarks: the site header (#nd-subnav) and this bar. axe
4.14.0 at 390px reported landmark-unique and landmark-no-duplicate-banner on
all 48 runs of the #308 sample (8 sections, en/zh-Hans/de, light/dark), on
the tree that already carried the other #308 fixes. The bar is a disclosure
for the page's headings, so the patch renders it as a <div>; no CSS selects
header by element, and the 390px screenshots do not change. Upstream 16.16.2
still renders it as a <header>.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FeA1nwBz1ohH65dvffUGKr
…e inside an English body

#305 localized the names of the heading-anchor and code-copy buttons. On
a fallback page both buttons sit in the page body, which #298 marks
lang="en", so "Ankerlink kopieren" was read with English rules: WCAG 3.1.2,
the inverse of what #305 fixed. On main at cf449fa that was 520 + 115
names per Latin-script locale over 55 fallback pages, and 336 + 58 in
zh-Hans and zh-Hant over 31.

Both buttons now take locale from the useI18n() they already read and
render it as their own lang (patches/fumadocs-ui@16.8.12.patch, the two
#305 hunks). On a page in the route locale this repeats what html lang
says; inside an English body it keeps the name in its own language.

check-locale-surface.mjs gains the inverse rule,
localized-name-marked-english: one of a locale's own names (read off its
built pages, the names that resolve to the locale, minus the English set)
inside a lang="en" region with no lang of its own fails. Two self-test
cases (red as #305 shipped it, green through an inherited lang), the
fixture body now carries a localized anchor button with its own lang, and
the live control feeds the inverse shape too, shown red with a reader
blind to lang on names.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FeA1nwBz1ohH65dvffUGKr
The "nothing built" guard in check-locale-surface.mjs fired artifact-missing
only when no locale directory held any HTML at all. The legal pages and the
locale roots live in the same directories, so a build (or a fixture tree)
holding them but no docs page fell through to one docs-page-html-missing per
page plus the blind-reader guards. #312 adds legal-page fixtures to every
self-test case, and with them this file's "docs pages not built" case read
exactly that way. Measured on this tip with #312's own two commits applied in
memory (git merge-tree --merge-base 4da46c5): before this change 1 self-test
case failed, after it 48 cases over 25 rules pass. The guard now asks whether
any <locale>/docs page was built.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FeA1nwBz1ohH65dvffUGKr
…ce; zh-Hant generated from zh-Hans

- privacy and terms on de, ja, es, fr and ko: the English entry is marked
  lang="en" (title, date, body, back link) and introduced by the #298 notice
  (ui-text notTranslated) in the route locale. en and zh-Hans render as before.
- The zh-Hans entry of each page moves, unchanged, into a zh-Hans.json beside
  it; gen-zh-hant converts it to zh-Hant.json like lib/ui-text, so
  /zh-Hant/privacy and /zh-Hant/terms show Traditional text and gen-zh-hant
  --check covers it. contentLocales, hreflang and the sitemap gain zh-Hant.
- check-locale-surface: STATIC_PAGES lists zh-Hant, and three rules read the
  built legal pages (legal-english-unmarked, legal-translation-shows-english,
  legal-page-unread), with self-test cases.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FeA1nwBz1ohH65dvffUGKr
…econd main

HomeLayout already renders main#nd-home-layout around its header and the page,
so the page's own main was a nested, duplicate landmark on /privacy and /terms
in every locale (axe: landmark-no-duplicate-main, landmark-main-is-top-level,
landmark-unique). Same classes, so nothing moves on screen. The
check-locale-surface legal fixture now mirrors that structure.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FeA1nwBz1ohH65dvffUGKr
…capability claim

Rule 3 ended "When in doubt, delete rather than leave behind", and the
Retiring a page section told authors never to leave a translation behind
for a page "rewritten to say something different". Read literally, both
delete a whole translated page over a changed port number, which
AGENTS.md step 2 and the new step 3 say stays with the translation pass.

Both now state the ruled line: an English correction that removes or
reverses a capability assertion deletes the page's locale siblings in
the same PR, and wording and number drift stay with the pass. Rule 3
links the ruling. GUIDE_REV is not bumped: no existing translation
output becomes invalid.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FeA1nwBz1ohH65dvffUGKr
…ack/spec 17.7.0

First half of the docs code-sample family close-out (#316): the samples on
eight English pages now parse with @objectstack/spec 17.7.0, the npm latest.

- workflows: the XState-style StateMachineConfig left the spec. A workflow
  is the `state_machine` validation rule on the object, with `requiredWhen`
  for "no resolved case without a resolution". The side-effect flow gets
  its labels and node configs. automation/index stops promising events
  and guards.
- approvals: labels on the start and end nodes, real `update_record`
  configs, and `resolveAs` moved onto the `expression` approver.
- actions: `Action` from `@objectstack/spec/ui`; the confirm question
  moves from `confirmText` to `description`. There is no flow node that
  calls an action and no view `actions` key, so the flow-step surface goes,
  placement is the action's `locations`, and a list view names
  `rowActions` / `bulkActions`.
- data/index: the stack namespace prefix, `inlineHelpText`, a `format` rule
  in place of field `pattern`, typed validation rules, the real ownership
  values and lifecycle shape, and no `decimal` type.
- relationships, formulas, validation-rules: `Field.masterDetail` and
  `Field.lookup` take the reference first, `Field.decimal` does not exist,
  option placeholders become real options, and fragments say what they are.

Locale siblings follow ruling A of #256: deleted for workflows,
automation/index, actions and data/index (18 files), where a capability
assertion is removed or reversed; kept where the change is code shape only.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FeA1nwBz1ohH65dvffUGKr
…17.7.0

Second half of the docs code-sample family close-out (#316), on 14 more
English pages.

- cel: the six one-liners become real slots (a formula field, a `script`
  rule's `condition`, `visibleWhen`, a start node's `condition`, a
  schedule's `expression`, a notify node's plain-text title), and the
  "where each tag is used" table names keys that exist. `tmpl` is not a
  notification slot: `NotifyConfigSchema.title` refuses it.
- agents: no `knowledge` block (removed in 17.0.0), memory is
  `longTerm.enabled` / `maxEntries` / `reflectionInterval`, and flows reach
  an agent through a `type: 'flow'` action.
- data-sources: a flat postgres config, no inline password (the secret is
  named in `external.credentialsRef`), `defineDatasource()`, a complete
  manifest, and a declared second datasource.
- apps: `mobileNavigation` was removed in 17.0.0. dashboards:
  `refreshIntervalSeconds`, and no `certified` measures.
- quickstart, record-access, rest-api, forms, pages, field-types, the two
  permission pages, objectql: builder signatures, required `fields`,
  real options, `requiredWhen`, and fragments that say what they are. The
  cel and objectql type signatures carry the doc-sample opt-out marker,
  an MDX comment that renders nothing.

Locale siblings follow ruling A of #256: deleted for agents, cel, apps and
dashboards (18 files), where a capability assertion is removed or reversed;
kept where the change is code shape only.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FeA1nwBz1ohH65dvffUGKr
`.github/scripts/check-doc-samples.mjs` reads every ts/js block on every
English page under content/docs and parses it with @objectstack/spec, as
#307 did by hand: statements evaluated as published with only TypeScript
syntax removed, every spec constructor call a parse, imports checked
against the entry point (type imports through the TypeScript compiler),
every flow's builtin nodes against their executor contracts, and a stack's
`requires` judged with the flows on its page. A fragment's first comment
says what it is, and the gate parses it inside the smallest whole it
belongs to. A block that is not a spec sample is N/A; a block that matches
no rule fails.

An MDX comment `{/* doc-sample: skip — why */}` right before a fence opts
one block out, for a counter-example or a type signature. It renders
nothing and is stripped from the llms bodies.

The spec is pinned exactly in `.github/scripts/doc-samples/` with its
npm lockfile, outside the pnpm workspace: as a devDependency of
tools/ci-scripts it re-resolved fumadocs-core's optional zod peer from
4.4.3 to 4.6.5 while fumadocs-mdx kept 4.4.3, two zod copies in the docs
build. CI installs it with `npm ci --prefix`, and Dependabot gets its own
entry for that directory, so a spec release that newly refuses a sample
turns its own bump red.

The gate's `--self-test` (51 cases, a pass and a refusal for each of the
22 fragment kinds) joins the runner in tools/ci-scripts. The CI step sits
next to the zh-Hant check, before type-check.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FeA1nwBz1ohH65dvffUGKr
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

2 participants