Repository navigation
docs: make the views and flows code samples parse with @objectstack/spec 17.7.0 - #315
Merged
Merged
Conversation
…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
…ests 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
This was referenced Oct 6, 2026
This was referenced Oct 6, 2026
Merged
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #307
Every code sample on
build/interface/views.mdxandbuild/automation/flows.mdxnow parses with the latest published@objectstack/spec, 17.7.0 (npmlatest, published 2026-10-06T12:22Z; versioned in objectstack4e4e8814, "chore: version packages (#21352)"). The check follows #301's method: extract the published blocks, wrap them minimally, parse them, run refusal controls. Results, before and after, are below.Two English pages change. No locale file, app code or script is touched.
What changed
build/interface/views.mdxKanban, Calendar, Gantt, Tree and Chart each get the top-level
columnsthat every list view requires (ListViewShapeSchema.columns,packages/spec/src/ui/view.zod.ts:2634). For Kanban, the value mirrors the card columns, as ObjectStack's own docs do (content/docs/ui/views.mdx, Kanban: "still required at the top level").Common list options:
fontWeight: 600becomesfontWeight: '600'. AconditionalFormattingstyle isz.record(z.string(), z.string())(view.zod.ts:2860).Fragments say what they omit. Each of the 8 single-view fragments starts with a comment: "One list view (or form view); the container and
dataare omitted (see above)". One sentence under the table says where such a view goes: adefineViewcontainer'slistor alistViewsentry, withdata. The omitted parts are not invalid:datais optional on a list view, and every fragment also parses withdatasupplied.The "List view types" table said only
gridneedscolumns. It also said which block keys are required, and it was wrong in both directions:kanban.columns(view.zod.ts:1809) andchart.values(:1841) are required, and the table omitted them;coverFieldandtitleField, calendartitleField, maplocationField, and treeparentFieldandlabelFieldare optional.The table now opens with "every list view needs a top-level
columns". It then lists each config block with its required keys, and the common optional keys in a column of their own.build/automation/flows.mdxThe card's finding 2 was read-only and named
send_email. Measured on 17.7.0, the premise holds only in part:send_emailis not aFlowNodeActionvalue (packages/spec/src/automation/flow.zod.ts:31-56).FlowNodeActionno longer gates a node'stypeat parse (ADR-0018,flow.zod.ts:9-19): a node of typesend_emailparses (control F3).defineFlowrefused was the whole sample shape:trigger,steps,inputs,type: 'manual' | 'scheduled', steps with noidorlabel, and per-stepaction,retryandonError. Of the 9 TS blocks, 6 failed. ThedefineStackblock passed on its own, the twotimeRelativefragments parsed, and the CEL block can only be shape-checked.So every flow sample is rewritten to
FlowSchema's graph (flow.zod.ts:977):nodesplusedges, with the trigger bound on the start node'sconfig. Where the prose explained the old shape, it was rewritten too.notifynode withchannels: ['email']— the documented email path.NotifyConfigSchemais atpackages/spec/src/automation/io-node-config.zod.ts:180, withchannels"default: inbox" at:224. ObjectStack's flows docs map the retired email action to anotifynode atcontent/docs/automation/flows.mdx:310. A stored template goes intemplate, in place oftitleandmessage.support_ticketand its recipient{record.assignee}. The oldsys_userwelcome email assumed that a record-change flow fires onsys_user, and nothing public says so.requires: ['automation', 'triggers'], up from['automation'].defineStackrefuses a stack that has a record-change or schedule flow and lacks'triggers'(control F1). Sources:flow-trigger-kind.ts:20, and ObjectStackflows.mdx:1849,1866.FlowSchema.typeenum (flow.zod.ts:1060),record_change | schedule | autolaunched | screen | api. The old table used Autolaunched for a record change, plus Scheduled and Manual.when: before_insert…table becomes the start node'striggerTypetokens (ObjectStackflows.mdx:2476-2484).before_*flows can mutate the record being written" is replaced. ObjectStack's guidance is that mutating the pending record before it is saved is a before hook's job, not a flow's (content/docs/automation/hooks.mdx:29).after_*flows run async" is dropped: no public source states it.runAs: 'system'is added. A scheduled run has no trigger user, so under the defaultuser, its data operations are refused (flow.zod.ts,runAsdescribe).update_recordwithmulti: true: without it, a predicate update is refused (builtin-node-config.zod.ts:495).approve_invoiceis anautolaunchedflow with anisInputvariable.POST /api/v1/automation/:name/trigger(AutomationApiContracts.triggerFlow,packages/spec/src/api/automation-api.zod.ts:674), withparams. The old route was the action door,/api/v1/actions/invoice/approve_invoice, with aninputsbody thatTriggerFlowRequestSchemawould drop silently (control F13).FlowNodeAction, plus the region and plugin types (parallel,try_catch,approval).decisionnode, a conditioned edge and anisDefaultedge (ObjectStackflows.mdx:1381-1420). The oldthen/elsestep had no counterpart.errorHandling: { strategy, maxRetries, backoffMs, backoffMultiplier }(flow.zod.ts:1200; ObjectStackflows.mdx:1500-1575). The old per-stepretryandonError: 'continue' | 'fail' | 'rollback'are removed: there is norollbackstrategy. "Failed flow runs land in the job retry queue" is removed too, because no public source states it.has(record.notes) && record.notes != ""becomes!isBlank(record.notes), with one sentence of why:recordis total over declared fields, sohas()is not a value test (ObjectStackflows.mdx, the total-binding callout).duration("30d")is gone:dis not a CEL duration unit.account.tierbecomesrecord.tier.os test --scenario "…"becomesos test qa/FILE.test.json.--scenariois not anos testflag (ObjectStackcontent/docs/deployment/cli.mdx:1937-1948).Every ObjectStack reference above is to the public repository at
4e4e8814, the 17.7.0 version commit.Parse results —
@objectstack/spec17.7.0 from npmThe checker is in the PM scratchpad:
spec-check/scripts/{extract,views-check,flows-check,controls}.mjs.How blocks are checked:
defineView,defineFlowordefineStackblock is evaluated as published, with only TS syntax removed (import,export,as const).ListViewSchema.parse(orFormViewSchema.parse),defineView({ list | form: fragment }), and with the omitteddatasupplied.getBuiltinNodeConfigContracts()). This is stricter thandefineFlow, which checks only that required keys are present.views.mdx — before (
main5d2f837): 9 pass, 18 fail · after: 27 pass, 0 faildefineViewshortest (28 → 28)defineStackregistration (52 → 52)defineViewnamed views (65 → 65)conditionalFormatting.0.style.fontWeight: expected string, received numbercolumns: invalid_union (required)columnscolumnscolumnscolumnsflows.mdx — before: 13 pass, 6 fail, 2 n/a · after: 27 pass, 0 fail, 1 n/a
defineStackrequires(22 → 27)requireswas never judged; control F1 shows['automation']is refused once a record-change flow is presentlabel,nodesandedgesmissing; unrecognizedtriggerandstepsdefineFlow) and PASS (executor contracts)type'scheduled'invalid; unrecognizedscheduleandstepslabelmissing on the flow,nodes.0andnodes.2timeRelativefragments (163 → 226)type'manual'invalid; unrecognizedinputsandstepsAutomationApiContracts.triggerFlow, the flow is on the page, the body parses asTriggerFlowRequestSchema, and everyparamskey is a declaredisInputidorlabel; unrecognizedwhen,thenandelseidorlabel; unrecognizedaction,inputs,retryandonErroros test(286 → 418)--scenariois not a flag)Refusal controls — 28 of 28 hold (26 refusal or acceptance controls, plus 2 baselines of the fixed shapes)
columnsis FAIL.fontWeight: 600is FAIL;'600'is PASS.valuesis FAIL.kanbanwithout its owncolumnsis FAIL.startDateFieldis FAIL.startDateFieldis PASS, sotitleFieldis optional.columsis FAIL, which shows the parse is strict.requires: ['automation']plus a record-change flow is FAIL; with'triggers'added it is PASS.trigger/stepsshape is FAIL.send_emailis PASS, butFlowNodeAction.parse('send_email')is FAIL, and so is a node carryingaction: 'send_email'.titlenortemplateis FAIL.subject/bodyspelling is PASS underdefineFlowand FAIL under the executor contract, which is what the extra check adds.strategy: 'retry'with nomaxRetriesis FAIL.onError: 'rollback'is FAIL.retryandonErrorare FAIL.os validateis what flags it.type: 'manual'andtype: 'scheduled'are FAIL.offsetDaystogether withwithinDaysis FAIL.{"inputs": …}body parses, andinputsis dropped.filtersin anupdate_recordconfig is FAIL.Page-text ablation, run on scratch copies; the repository is untouched:
columnsline (anchor count 1 → 0) turns the views check red: EXIT 1, block 5 FAIL ×3.requiresback to['automation'](mutant marker 1, anchor 0) turns the flows check red: EXIT 1, refused for the 4 flows with triggers.Locale siblings — ruling A of #256
columnsis added,fontWeightis retyped, and the table's required-key lists are corrected. No view type, key or capability that the page asserts is removed.7fe2a7f…, while the base English source is4616870…, changed by Docs polish from the 2026-10-06 audit: muted-text contrast, the License table on phones, FAQ formatting, search ranking, glossary order, licence/license spelling #301. They stay stale.5d2f837) already deleted all 7 under ruling A, for theai_callremoval (git log --diff-filter=D -- 'content/docs/build/automation/flows.*.mdx'→5d2f837). The removed or reversed assertions:send_emailaction;rollbackerror mode;manualtype;os test --scenario;{!org.FIELD}interpolation;Verification (at
4d12d0a)pnpm turbo run type-check --continue --force✓ Types generated successfully· 1 successful, 0 cachedNEXT_PRIVATE_STANDALONE=true pnpm turbo run build --forcepnpm turbo run test --force✓ 10 self-test(s) passedcheck-locale-surface.mjs✓ every advertised URL has a source file and every source file is advertised; …check-positioning.mjs✓ positioning: 4 copies equal their constants; the brand is right in 659 pages and 2 llms bodies; no stale sentence in 79 English sources …check-search-locales.mjs✓ 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 noncegen-zh-hant.mjs --check✓ zh-Hant: 60 generated file(s) match the zh-Hans sources byte for byte.git diff --name-status --no-renames origin/main...HEAD,--actor hotlong,TRANSLATION_BOT_LOGINset✓ 2 file(s) changed, no translation artifacts touched.The control, with the bot as actor, is EXIT 1check-translations.mjs✓ translations gate passed. Stale is reported only: zh-Hans 47, the others 24, the same as basecheck-translation-output.mjs --self-test/--files✓ translation output gate passed (256 pre-existing finding(s) reported)check-node-floor.mjs(with--self-test) /check-half-states.mjs --self-test1551 cases passBrowser.
next startserved the build on :3307, and Chromium (Playwright) loaded both pages at 1440 and 390: HTTP 200, the correct h1, and 11 code blocks per page.scrollWidthequals the viewport at both widths (1440/1440, 390/390).overflow-x: auto), as everywhere else on the site.NOT MEASURED:
docs.objectstack.ailinks: egress answers CONNECT 403. The anchors#hooks-vs-flowsand#the-acting-organizationare derived from the public source headings, which ObjectStack's own pages link to.Proposed, not built: a docs-sample parse gate
The same defect class keeps reaching the site: #301, this card, and the pages listed under Acceptance notes. A gate would catch it on the PR that introduces it and on the spec bump that breaks it.
.github/scripts/check-doc-samples.mjswould extract the fencedtsblocks from Englishcontent/docs/**/*.mdxand evaluate the ones that opt in. Opt-in would be a fence meta word, such as atsfence taggedspec-check. That is not an MDX comment, which the llms bodies must not carry.defineView/defineFlow/defineStackas written; a single-view fragment as a list or form; flow-key fragments with the nodes their comment names; plus the executor-contract pass over every builtin node.@objectstack/specwould be pinned as a devDependency oftools/ci-scripts, so a dependabot bump that newly refuses a sample turns its own PR red.--self-testwith fixtures for each rule, plus this PR's before pages as the negative control that must go red.Acceptance notes
Family members not fixed here, measured on 17.7.0 and left for a family close-out:
build/automation/workflows.mdx:104:defineFlowrefused —labelmissing on the flow,nodes.0andnodes.3.build/automation/approvals.mdx:216:defineFlowrefused —nodes.0.labelandnodes.4.labelmissing.build/interface/actions.mdx:119:defineView({ name, object, actions })refused — unrecognizedactions. Lines 63 and 127-133 show the old{ type: 'action', action, inputs, record }step.reference/cel.mdx: the "Flow guard" sample,{ when: P… }, is the old step shape.Left alone on views.mdx. These parse, so they are not this defect class:
exportOptions: ['csv','xlsx']is the legacy bare-array spelling; it lifts to{ formats }.filterableFieldsis a "legacy shorthand — prefer userFilters".visibleOn, which is deprecated in favour ofvisibleWhenand normalized at parse.Pre-existing. At 1440, #301's named-views block has one line (
quick:) that scrolls inside its box.Branch.
staged/issue-307is cut from5d2f837, per the claim.origin/mainis nowcf449fa(#313, #314).git merge-tree --write-tree origin/main HEADis clean (tree0087435), and no path is shared.Branch
69f7f1eonclaude/pm-dispatch-objectos-ju9td1, a fast-forward with no force. It starts at the remote branch head41a1994, mergesorigin/main@cf449fa(fix(docs): run the awaited fumadocs-mdx write after next typegen in type-check #313, docs: localize fumadocs' accessible names, mark English page-tree entries, zh-Hant 404, Open-in links follow the page shown #314; after the merge the diff againstmainis empty), and cherry-picks4d12d0a, which was cut from5d2f837.git diff origin/main 69f7f1eis byte-identical togit diff 5d2f837 4d12d0a; no path is shared with fix(docs): run the awaited fumadocs-mdx write after next typegen in type-check #313 or docs: localize fumadocs' accessible names, mark English page-tree entries, zh-Hant 404, Open-in links follow the page shown #314.Generated by Claude Code