Skip to content

showcase: no view authors an emptyState, so the objects._views.<view>.emptyState translation surface has no fixture to test #7714

Closed

Description

@huangyiirene

Observation-class finding, filed unassigned per Prime Directive #10. Found while reconciling the i18n checklist for #7686 (item 3); recorded there as a waiver, not fixed, because showcase code is outside that card's declared file surface.

What

packages/spec/src/system/translation.zod.ts declares a per-view empty-state translation surface:

objects.<object>._views.<view_name>.emptyState.title
objects.<object>._views.<view_name>.emptyState.message

No view in examples/app-showcase authors an emptyState at all — a grep for emptyState across examples/app-showcase/src returns nothing, in the view definitions and in src/system/translations/index.ts alike. The declared group therefore has no reachable instance on the stock fixture: a zero-row view renders the console's own built-in placeholder, which is console chrome and not a resolution of this key.

Why it is worth writing down

The QA run #7640 tried to drive this variant of i18n.surface-matrix and could not — there is no translated key to render, so the clause lands blocked(fixture) on every run rather than testing the resolver. The checklist now waives it explicitly (i18n.surface-matrix → fixtures.knownGaps, revision 3), which stops each sweep from rediscovering it, but the waiver is a debt marker: paying it down means authoring one emptyState onto a showcase view plus its zh-CN bundle entry, after which the waiver flips back to a live clause.

Note examples/app-showcase/src/coverage.ts does not track emptyState either, so the showcase's demonstrated-or-waived ratchet does not currently see this gap from its side.

Not a defect today

Nothing a user hits: the spec declares the surface, and an app that authors an emptyState presumably gets it translated — that is exactly what no fixture proves either way. The cost is a permanently unprovable clause in the i18n matrix. Recording it so triage grades it rather than leaving it inside a checklist waiver.

Options, not a decision

  • Author the fixture — add emptyState: { title, message } to one showcase view (a filtered slice that is genuinely empty on seed data reads best) and the matching objects.<object>._views.<view>.emptyState entry to the zh-CN bundle. Un-waives the clause and demonstrates the surface.
  • Leave it waived — accept that this one group is asserted by the spec's own tests rather than by an end-to-end run, and keep the knownGap as the record.

Source: QA run #7640 · checklist card #7686.

Activity

  1. added
    findingRecorded observation — held, not dispatchable until the findings triage round grades it
    on Aug 11, 2026
  2. added
    domain:devxDomain lane — seat card indexed by label:pm:seat
    on Aug 11, 2026
  3. claude commented on Aug 11, 2026

    @claude
    Contributor

    Findings triage — routing repair only (route A): appended domain:devx. The finding grade is unchanged and no ownership is taken.

    Landing anchor: paying the debt means authoring one emptyState onto a showcase view plus its zh-CN bundle entry — examples/app-showcase/src/** QA-fixture work, routed to the devx lane like the sibling seed-fixture card #7746.

    Stale-premise check on origin/main @ 8f1851e: git grep emptyState -- examples/app-showcase/src returns zero hits (counter-check that the scanner works: the surface itself is declared at packages/spec/src/system/translation.zod.ts:185-186, and the alias table at :191 maps empty/emptyMessage onto it). The fixture gap stands as filed.

    本评论来自分诊座位 Routine(#5474 试点),不构成认领。


    Generated by Claude Code

  4. os-zhuang commented on Aug 11, 2026

    @os-zhuang
    Contributor

    Findings triage round, 2026-08-11 (maintainer-directed). Graded: hold — fixture gap only, no user-facing behaviour; take it with the next i18n checklist round or showcase view work, where authoring one emptyState costs nothing extra.


    Generated by Claude Code

  5. added
    pm:queueReady for the PM dispatch loop
    and removed
    findingRecorded observation — held, not dispatchable until the findings triage round grades it
    on Aug 12, 2026
  6. added theissue type on Aug 12, 2026
  7. hotlong commented on Aug 12, 2026

    @hotlong
    Contributor

    Findings-round promotion: pm:queue (domain:devx kept, type Task).

    The hold's ride-along strategy has one measured miss: the named sibling showcase-fixture card #7746 closed 2026-08-11 via merged PR #7789 without picking this up — so "take it with the next showcase work" demonstrably doesn't self-execute, and the card promotes on its own.

    Premise re-verified on origin/main: zero emptyState hits across all of examples/, while the surface is fully declared (translation.zod.ts:185-195), authorable (view.zod.ts:1459) and documented (references/ui/view.mdx:511,599). Landing: one emptyState on a showcase view (examples/app-showcase/src/ui/views/*.view.ts) + its en/zh-CN entries under _views.<view>.emptyState.{title,message} in src/system/translations/index.ts, which already carries _views blocks to extend. Size XS.

    Lane note (flag only): examples/** sits loosely in devx by QA-fixture precedent (#7746) — precedent kept, not re-litigated here.

    本评论来自分诊座位(scheduled session session_01DLzE5XVRASxFotEU1h9cjH),不构成认领。


    Generated by Claude Code

  8. added
    pm:dispatchedDispatched to a dev agent by /pm-dispatch
    and removed
    pm:queueReady for the PM dispatch loop
    on Aug 12, 2026
  9. self-assigned this
    on Aug 12, 2026
  10. hotlong commented on Aug 12, 2026

    @hotlong
    Contributor

    Claim: PM loop round 3
    Session: session_01PaiisQMhsYxwa5ZX6Mfmv2
    Branch: claude/issue-7714-showcase-emptystate-fixture
    Worktree: objectstack-issue-7714
    Domain: domain:devx
    File surface: examples/app-showcase/src/** only — one view file + src/system/translations/index.ts (stop on breach; explain in the report)
    Container & model: XS, mode:cloud, model: opus
    Serial constraints cleared: no open PR touches examples/app-showcase/**. Sibling card #7811 claimed the same round is content/docs/permissions/** — disjoint. ⚠️ #7923 deliberately NOT claimed this round (see below) partly because its example-showcase test-layer half sits adjacent to this surface.

    Premise re-verified on origin/main before claiming — independently of triage's check, which reached the same answer: zero emptyState occurrences anywhere under examples/, while the surface is declared (translation.zod.ts:185-195), authorable (view.zod.ts:1459) and documented (references/ui/view.mdx:511,599). The fixture gap stands.

    Why this promoted rather than staying on hold. Its 2026-08-11 hold said "take it with the next i18n checklist round or showcase view work". Triage measured that the named sibling — showcase-fixture card #7746 — closed via merged PR #7789 without picking this up. That is a hold whose restart condition had no reader, which is the failure mode this lane's protocol names explicitly: an opportunistic restart condition must name trigger files, or it never fires. Recording it because the lesson outlives the card.


    Generated by Claude Code

  11. hotlong commented on Aug 12, 2026

    @hotlong
    Contributor

    Dispatched — PM loop round 3, mode:cloud.

    Cloud session: session_01Vs1H5ouuLLAtyZLALg4H7H · model claude-opus-5 · outcome branch claude/issue-7714-showcase-emptystate-fixture.

    Recording the session id so a successor to this seat can probe liveness without it.

    Dispatch carries: fixture-only in examples/app-showcase/src/** (⛔ no packages/spec — the surface is already declared and authorable, this card exists because nothing uses it); pick a view where an empty state is genuinely plausible rather than whichever file is shortest; and confirm the key's shape from the schema before authoring, since wrong nesting produces a fixture that validates as absent — closed-looking, gap still open.


    Generated by Claude Code

  12. hotlong commented on Aug 12, 2026

    @hotlong
    Contributor
    {
      "issue": 7714,
      "status": "done",
      "branch": "claude/issue-7714-showcase-emptystate-fixture",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/8036",
      "premise_still_valid": true,
      "summary": "Premise re-derived independently, not taken from the prompt or triage: `git grep emptyState -- examples/` returns zero hits, while the surface is declared at packages/spec/src/system/translation.zod.ts (a `_views` record whose entries carry an optional `emptyState` strictObject of `{title?, message?}`, with an alias table folding empty/emptyMessage onto it) and authorable on a list view at packages/spec/src/ui/view.zod.ts (title/message are I18nLabelSchema, so a bare string is valid; there is also an `icon`, left unused). Fixture authored on showcase_task.listViews.urgent, plus `en` and `zh-CN` entries under `_views.urgent.emptyState` in examples/app-showcase/src/system/translations/index.ts. Two files, +59/-1, no spec change. VIEW CHOICE (the substance of the card): emptiness means different things on a filtered saved view than on an unfiltered object list. Unfiltered-and-empty is a SETUP state ('nothing exists yet') which the showcase seed deliberately never produces — data/seed/index.ts seeds inquiries specifically so 'every view renders real data'. Filtered-and-empty is a STEADY state ('nothing matches right now') that a live deployment hits constantly, and that is the case per-view empty-state copy exists to serve. `urgent` is the strongest instance: empty is the outcome the user WANTS, so the copy reports good news ('No urgent tasks' / 'Nothing needs immediate attention right now') rather than nagging about absent data — a message a maintainer will believe; and it stays reachable without touching the seed, since the two urgent seed rows are exactly what the neighbouring bulk_actions view's showcase_mark_done exists to clear, so a demo user empties the view by using the app. JUDGMENT CALL, recorded because it deviates from repo convention: the `en` side of this bundle carried no `_views` block at all (all fifteen live only on zh-CN). Block 1 binds me to author both arms, so I added an `en` `_views` block containing ONLY the emptyState group — not a mirror, because view labels are already English in the view file and restating all fifteen would be fifteen fake translations of exactly the kind this bundle's own header warns against. The emptyState group earns its arm because a key present in one locale cannot demonstrate a locale-resolving surface: an `en` session would otherwise read the view's inline source string through the fallback path, a different code path.",
      "tests": "Gates derived via `node scripts/pm/dispatch-gates.mjs` on both touched paths — it named NO path-scoped family ('No check family names the given paths in its own source'), so the implicated families were run by convention. All under flock + NODE_OPTIONS=--max-old-space-size=4096. (1) `pnpm --workspace-concurrency=2 --filter '@objectstack/example-showcase...' build` (dependency direction, i.e. showcase + its deps) — GREEN, and this is the showcase's own metadata validation, the likely failure mode for a malformed view: 'UI: 1 Apps 6 Views 28 Pages ... Artifact: dist/objectstack.json (683.3 KB) ... Done'. (2) `check:i18n-coverage` — 'OK (12 config(s), 660 baselined untranslated string(s), none new)'. (3) `check:i18n` — 'OK (9 package(s) - all bundles in sync, no undeclared authoring keys)'. (4) `check:nul-bytes` — 'OK (scanned 7331 text file(s) ... no raw ASCII control bytes)'. REVERSE VERIFICATION, direction predicted before running: packages/cli/src/utils/i18n-extract.ts emits `_views.VIEW.emptyState.title/.message` as real expected entries (pushEntry, not the derived variant), so the prediction was RED by exactly two if the bundle entries were dropped while the view kept its emptyState. Reverted only the translations file (git checkout + patch file — never git stash), re-ran: 'examples/app-showcase/objectstack.config.ts: untranslated declared strings grew 451 to 453', EXIT=1. +2 exactly as predicted. That is what proves the fixture genuinely reaches the extractor instead of validating as absent, and it is why both keys are translated in the same change: untranslated widens the frozen count and fails the ratchet, while translating anything extra pushes it below baseline, which the same gate rejects as an un-ratcheted improvement. Restored via `git apply`, both gates re-confirmed green (COV_EXIT=0, NUL_EXIT=0). NOTE ON THE GATE'S OUTPUT: it warns that piping it reports the PIPE's status, so every run above captured to a file and echoed $? explicitly. No changeset: @objectstack/example-showcase is `private: true`, which scripts/check-changeset-fixed.mjs excludes by design. `skip-changeset` applied as a read-then-union write; labels read back after the write as [\"size/s\",\"skip-changeset\"], so the bot-applied size label survived. CI at report time: only Vercel had reported (success, 'Canceled by Ignored Build Step'); the rest in_progress — the convergence wait is the PM's per the 2026-08-10 ruling.",
      "open_questions": [],
      "out_of_scope_findings": [
        "NOT FILED, deliberately, and flagged for the PM rather than turned into an issue — stating this plainly because the template's presumption (every finding becomes a #N) does not fit here. Observation: nothing in THIS repo resolves `_views.VIEW.emptyState`. packages/spec/src/system/i18n-resolver.ts resolves `_views.KEY.label` and `.description` only, and a repo-wide grep finds emptyState solely in the two zod schemas, the typegen, the extractor, and tests. The consumer is the objectui list renderer, which is a sibling repo not attached to this session, so I cannot tell whether the label/emptyState asymmetry is by design (emptyState rendered wholly client-side) or a real hole. Filing 'framework has no emptyState resolver' without being able to read objectui would be unverified noise, and os-dev filing discipline says search-and-verify before filing. This does NOT affect the card: the gap filed was the missing fixture, and the extractor/coverage path demonstrably does read the key (the 451 to 453 reverse verification above). Recorded because a checklist item that expects a framework-side resolver call would be looking in the wrong repo. Also recorded: the issue BODY was verified complete and not truncated at rest — GitHub's stored body renders in full through the What / Why it matters / Not a defect / Proposed solutions sections. The `mcp__github__issue_read` tool's own output truncated mid-fence at `objects.` where the angle-bracket placeholder begins, which reads exactly like sanitizer damage but is not; confirmed by fetching the rendered issue page."
      ]
    }

    Generated by Claude Code

  13. hotlong commented on Aug 12, 2026

    @hotlong
    Contributor

    ACCEPT — PR #8036. Reviewed against GitHub, not against the report's self-description.

    check result
    path surface (get_files) 2 files, both examples/app-showcase/src/** (ui/views/task.view.ts, system/translations/index.ts). Zero packages/spec — ruling 1 honoured
    size +59 / −1
    labels size/s, skip-changeset
    changeset correctly none — @objectstack/example-showcase is private: true, excluded by check-changeset-fixed.mjs by design

    The reverse verification is what makes this acceptable

    The dispatch's stated risk was that wrong nesting yields a fixture that validates as absent — closed-looking, gap still open. The dev tested exactly that, with the direction predicted before running: i18n-extract.ts emits _views.VIEW.emptyState.title/.message as real expected entries, so dropping the bundle entries while keeping the view's emptyState must widen the untranslated count by exactly two. Measured: "untranslated declared strings grew 451 to 453", EXIT=1. +2, exactly as predicted, then restored and re-confirmed green.

    That is the difference between "I added a key" and "I proved the key is reachable by the machinery that consumes it."

    Two process details worth noting because both are recorded pitfalls in this lane: the revert used git checkout + a patch file with an explicit "never git stash", and skip-changeset was applied as a read-then-union write with labels read back afterwards — so the bot-applied size/s survived instead of being clobbered.

    The view choice earned the card

    showcase_task.listViews.urgent, with a real argument rather than convenience: an unfiltered empty list is a setup state ("nothing exists yet") that the showcase seed deliberately never produces; a filtered empty view is a steady state ("nothing matches right now") that a live deployment hits constantly. On this particular filter, empty is the outcome the user wants, so the copy reports good news instead of nagging about missing data — and it stays reachable without touching the seed, because the neighbouring bulk_actions view's showcase_mark_done is exactly what clears the two urgent seed rows. A demo user empties the view by using the app.

    The recorded judgment call is also right: the en arm got a _views block containing only the emptyState group, deliberately not mirroring the other fourteen view labels, since those are already English in the view file and restating them would be fourteen fake translations of the kind the bundle's own header warns against. But emptyState does earn its en arm, because a key present in one locale only cannot demonstrate a locale-resolving surface — an en session would fall back to the view's inline string, a different code path.

    Its one un-filed observation: resolved, and it was right not to file

    The dev observed that nothing in this repo resolves _views.VIEW.emptyState (i18n-resolver.ts handles .label / .description only) and declined to file, because the consumer is objectui — a sibling repo not attached to its session — and filing "the framework has no emptyState resolver" without being able to read objectui would be unverified noise.

    Correct call, and I closed the loop with the access it lacked. objectui is attached to this seat's session, and it does resolve the key:

    • packages/i18n/src/__tests__/useObjectLabel-view.test.tsx:125 — "goes through viewSuffixes (label / description / emptyState)"
    • packages/app-shell/src/views/ObjectView.defaultViewIdentity.test.tsx:156 — "resolves the default list label/description/emptyState under _views.default"
    • packages/app-shell/src/views/ObjectView.tsx:1686-1691 — viewDef.emptyState ?? listSchema.emptyState
    • packages/app-shell/src/views/InterfaceListPage.tsx:383 — emptyState: view.emptyState

    The asymmetry is by design: emptyState's locale resolution lives in objectui's viewSuffixes, not in the framework resolver. No card is warranted — recording the conclusion here so nobody re-derives it. This is the second time this term that searching before filing correctly produced no card; with the findings pool near 44, that restraint has real value.

    New pitfall it surfaced, added to this seat's record

    The dev reported that mcp__github__issue_read's own output truncated mid-fence at an angle-bracket placeholder — "reads exactly like sanitizer damage but is not" — and it confirmed the stored body was intact by fetching the rendered page. That matters because every dispatch this seat writes tells the dev to "verify the body is not truncated": that instruction can now be known to produce false positives, and the disambiguation is to check the rendered issue rather than trusting the tool's transport.

    Marking ready and arming auto-merge. Tracked to MERGED, with the closed_by_pull_requests check on landing.


    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

domain:devxDomain lane — seat card indexed by label:pm:seatpm:dispatchedDispatched to a dev agent by /pm-dispatch

Type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions