You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Percent field: a display key that shows a plain percentage instead of the progress bar, in list cells and on the record page #27973
Filing gate: ③ triage-directed. The order on #27430 (rows 2–4) said: TAKE what is ui-only, CARD the rest. The card thread measured each row on origin/mained0e71db (report 6515939841, out_of_scope_findings). Row 2 is taken in PR #27972.
Reach: any percent field that is a rate or a probability rather than progress: HotCRM crm_opportunity.probability and the campaign rate fields (#27430 row 3).
Reader who acts: domain:spec first (the key; Clause-②: yes, widening; Metadata-protocol added), then domain:ui (the renderer reads it).
Duplicate search: search_issues "percent field progress bar plain percentage display PercentCellRenderer" → #5066 only (closed; chip clipping, a different gap).
Observed
PercentCellRenderer (ui/packages/fields) draws a progress bar for every percent value, with no key to turn it off. It is the face that list-grid cells and the record page's percent fields (DetailView, HeaderHighlight chips) share. Nothing existing selects a plain face:
Field.format has no declared vocabulary and only promotes textual types.
Field.widget is the form override.
ListColumn.type is a renderer override whose number face would print the stored fraction (0.25), not 25%.
Wanted
A declared display key on the field (and optionally on the list column), for example a percent display mode, with the bar as the default. The spec half: the key, its describe text and docs. The ui half (a follow-up domain:ui card once the key ships): PercentCellRenderer reads it on every host. Note that ui/packages/fields/src/index.tsx is held today by domain:ui seat 4's PR #27912.
Dedupe words: percent progress bar, percent display, plain percentage, probability bar, PercentCellRenderer.
Claim: PM loop round 2
Session: session_01L9qibx5r5nhDMwpRs4SL6D
Account: os-tesla
Branch: claude/project-thread-hosvni (the card thread's designated push branch, the recorded platform exception to claude/issue-<n>-<slug>)
Worktree: objectstack-issue-27973
Domain: domain:spec
Seat: domain:spec#2
File surface: packages/spec/src/data/field.zod.ts (the percent display key, field level only), tests, describe, regenerated spec artefacts and reference docs, changeset (@objectstack/spec minor). The ui half is a follow-up domain:ui card the seat files when the key lands; ⛔ no ui/** and no governed path (stop on breach; explain in the report)
Container & model: S, mode:thread, model: TIER_DEFAULT (Clause-②: yes ⇒ default judgment tier to build; the contract review at CONTRACT_REVIEW_TIER is the seat's own, on the maintainer's 「你有 fable,可以自己契约复审」), effort: high
Clause-②: yes
Metadata-protocol: added
Responsibility: n/a — not a defect card
Thread-read: none
Premise: holds (re-read at origin/main9f13ee81; the declaration named on the card still lacks the key)
Serial constraints cleared: open PR #28096 (seat 4, currencyConfig.homeAmount) edits field.zod.ts in a different region: ordinary concurrency, the later lander merges main
Ruling-ref: none on the shape; dispatch authority = the maintainer's 「p3 也可以开发」 (2026-10-30T12:41Z) and 「每个座位并发可以5」 (2026-10-30T15:04Z); the card's filing gate ③ (triage-directed)
Dispatched 2026-10-30T15:09Z to a card thread of the seat's project; the order lives in the project. ⛔ The thread posts no claim and writes no assignee.
{
"issue": 27973,
"status": "done",
"branch": "claude/project-thread-hosvni",
"pr": "https://github.com/objectstack-ai/objectstack/pull/28222",
"session": "session_01L9qibyu6UsPddURvKQb3fE",
"premise_still_valid": true,
"summary": "FieldSchema declares one optional key, percentDisplay: z.enum(['bar','plain']).optional(), beside useGrouping (the other flat numeric display hint). Absent = the renderer default (the progress bar); bar pins it; plain shows the percentage as text alone. Display only, no default, not type-restricted at parse (useGrouping's posture); the describe says the Studio UI does not read it yet. Liveness row percentDisplay is planned (no authorWarn, as useGrouping was) with its flip condition in the note. Spec half only; no ui/** edits, no governed paths, no list-column key.",
"tests": "head 2f944c8d: spec vitest src/data + scripts/liveness under the lock: Test Files 161/161, Tests 4975 passed, 1 todo. spec typecheck: lock VERDICT command-exit 0. Ablation via scripts/ablation-replace.mjs (suite reads src): z.enum(['bar','plain']) -> z.enum(['bar']), anchor 1 -> 0 on disk; -t percentDisplay -> 4 failed, 3 passed; restored, blob == HEAD, git diff HEAD empty.",
"gates": "dispatch-gates --commands at 2f944c8d: 104 families, stdin from /dev/null, exit codes recorded before any pipe (record: /mnt/project-files/spec-seat-2/r2026-10-30/out/27973-gates-ran-2f944c8d.list). --ran: 104 derived, 104 run, 0 NOT-MEASURED, 0 red. Exit 3 then 0 after the prerequisite: check-plugin-teardown-shape --self-test (pinned control 621a4876 fetched into the shallow clone, first pass); spec check:skill-examples (final pass; client dist predated a merged client source change, rebuilt @objectstack/client-react... with dts). Same 104 also all exit 0 at 0c8ff3e4 and 39dd0028. spec check:generated 14/14 up to date after each merge of origin/main. CI check-runs on #28222: in_progress at report time.",
"dogfood_battery": "n/a — no `!` in the PR title and no (narrowing) arm",
"line_budget": "+110 / -1 across 9 files vs origin/main",
"files_changed": [
".changeset/27973-percent-display.md",
"content/docs/references/data/field.mdx",
"content/docs/references/data/object.mdx",
"content/docs/references/system/migration.mdx",
"packages/spec/authorable-surface/data.json",
"packages/spec/liveness/field.json",
"packages/spec/liveness/state-counts/field.md",
"packages/spec/src/data/field.test.ts",
"packages/spec/src/data/field.zod.ts"
],
"deviations": [
"none against the order. Skills-docs: follow-up owed — skills/objectstack-data/rules/field-types.md (its percent row lists the type's keys); the seat files it with the renderer half."
],
"decision_for_maintainer": "Chosen: a flat optional enum percentDisplay ('bar' | 'plain') on FieldSchema beside useGrouping, because no per-type display member exists for numeric types (currencyConfig is currency-only and about which currency). Not taken: a nested percentConfig block (one member does not earn a container), a Field.format vocabulary (format is declared vocabulary-free), a list-column override (out of this PR by order; only owed if a list needs a different face from the record page), authorWarn on the liveness row (harmless display fallback, useGrouping precedent, and a warned field row re-opens the lint field walk outside this file surface).",
"mcp_calls": "2 — mcp__github__issue_read (get, get_comments) on #27973; 0 writes",
"api_writes": "2 — relay pr_create (fleet-write run 41098688570, PR #28222); this os-dev-report comment via post-stamped",
"open_questions": [],
"out_of_scope_findings": [
"carrier: the seat · the ui half, a domain:ui follow-up card: PercentCellRenderer (ui/packages/fields/src/index.tsx) reads percentDisplay on every host; the grid/detail relay sites that copy useGrouping onto the cell's field metadata copy it too; field.form.ts gains the row; liveness flips planned -> live and the describe drops its not-yet-read clause · dedupe words: percentDisplay, PercentCellRenderer plain percentage, percent progress bar"
]
}
Activity
objectstack-fleet commentedon Oct 30, 2026
domain:spec#2 · os-tesla · session_01L9qibx5r5nhDMwpRs4SL6D
Claim: PM loop round 2
Session:
session_01L9qibx5r5nhDMwpRs4SL6DAccount:
os-teslaBranch:
claude/project-thread-hosvni(the card thread's designated push branch, the recorded platform exception toclaude/issue-<n>-<slug>)Worktree:
objectstack-issue-27973Domain:
domain:specSeat:
domain:spec#2File surface:
packages/spec/src/data/field.zod.ts(the percent display key, field level only), tests, describe, regenerated spec artefacts and reference docs, changeset (@objectstack/specminor). The ui half is a follow-updomain:uicard the seat files when the key lands; ⛔ noui/**and no governed path (stop on breach; explain in the report)Container & model:
S,mode:thread,model: TIER_DEFAULT(Clause-②: yes⇒ default judgment tier to build; the contract review atCONTRACT_REVIEW_TIERis the seat's own, on the maintainer's 「你有 fable,可以自己契约复审」),effort: highClause-②: yes
Metadata-protocol: added
Responsibility: n/a — not a defect card
Thread-read: none
Premise: holds (re-read at
origin/main9f13ee81; the declaration named on the card still lacks the key)Serial constraints cleared: open PR #28096 (seat 4,
currencyConfig.homeAmount) editsfield.zod.tsin a different region: ordinary concurrency, the later lander mergesmainRuling-ref: none on the shape; dispatch authority = the maintainer's 「p3 也可以开发」 (2026-10-30T12:41Z) and 「每个座位并发可以5」 (2026-10-30T15:04Z); the card's filing gate ③ (triage-directed)
Dispatched 2026-10-30T15:09Z to a card thread of the seat's project; the order lives in the project. ⛔ The thread posts no claim and writes no assignee.
objectstack-fleet commentedon Oct 30, 2026
domain:spec#2 · os-tesla · session_01L9qibyu6UsPddURvKQb3fE
os-dev-report
Draft PR #28222 at head
2f944c8d.{ "issue": 27973, "status": "done", "branch": "claude/project-thread-hosvni", "pr": "https://github.com/objectstack-ai/objectstack/pull/28222", "session": "session_01L9qibyu6UsPddURvKQb3fE", "premise_still_valid": true, "summary": "FieldSchema declares one optional key, percentDisplay: z.enum(['bar','plain']).optional(), beside useGrouping (the other flat numeric display hint). Absent = the renderer default (the progress bar); bar pins it; plain shows the percentage as text alone. Display only, no default, not type-restricted at parse (useGrouping's posture); the describe says the Studio UI does not read it yet. Liveness row percentDisplay is planned (no authorWarn, as useGrouping was) with its flip condition in the note. Spec half only; no ui/** edits, no governed paths, no list-column key.", "tests": "head 2f944c8d: spec vitest src/data + scripts/liveness under the lock: Test Files 161/161, Tests 4975 passed, 1 todo. spec typecheck: lock VERDICT command-exit 0. Ablation via scripts/ablation-replace.mjs (suite reads src): z.enum(['bar','plain']) -> z.enum(['bar']), anchor 1 -> 0 on disk; -t percentDisplay -> 4 failed, 3 passed; restored, blob == HEAD, git diff HEAD empty.", "gates": "dispatch-gates --commands at 2f944c8d: 104 families, stdin from /dev/null, exit codes recorded before any pipe (record: /mnt/project-files/spec-seat-2/r2026-10-30/out/27973-gates-ran-2f944c8d.list). --ran: 104 derived, 104 run, 0 NOT-MEASURED, 0 red. Exit 3 then 0 after the prerequisite: check-plugin-teardown-shape --self-test (pinned control 621a4876 fetched into the shallow clone, first pass); spec check:skill-examples (final pass; client dist predated a merged client source change, rebuilt @objectstack/client-react... with dts). Same 104 also all exit 0 at 0c8ff3e4 and 39dd0028. spec check:generated 14/14 up to date after each merge of origin/main. CI check-runs on #28222: in_progress at report time.", "dogfood_battery": "n/a — no `!` in the PR title and no (narrowing) arm", "line_budget": "+110 / -1 across 9 files vs origin/main", "files_changed": [ ".changeset/27973-percent-display.md", "content/docs/references/data/field.mdx", "content/docs/references/data/object.mdx", "content/docs/references/system/migration.mdx", "packages/spec/authorable-surface/data.json", "packages/spec/liveness/field.json", "packages/spec/liveness/state-counts/field.md", "packages/spec/src/data/field.test.ts", "packages/spec/src/data/field.zod.ts" ], "deviations": [ "none against the order. Skills-docs: follow-up owed — skills/objectstack-data/rules/field-types.md (its percent row lists the type's keys); the seat files it with the renderer half." ], "decision_for_maintainer": "Chosen: a flat optional enum percentDisplay ('bar' | 'plain') on FieldSchema beside useGrouping, because no per-type display member exists for numeric types (currencyConfig is currency-only and about which currency). Not taken: a nested percentConfig block (one member does not earn a container), a Field.format vocabulary (format is declared vocabulary-free), a list-column override (out of this PR by order; only owed if a list needs a different face from the record page), authorWarn on the liveness row (harmless display fallback, useGrouping precedent, and a warned field row re-opens the lint field walk outside this file surface).", "mcp_calls": "2 — mcp__github__issue_read (get, get_comments) on #27973; 0 writes", "api_writes": "2 — relay pr_create (fleet-write run 41098688570, PR #28222); this os-dev-report comment via post-stamped", "open_questions": [], "out_of_scope_findings": [ "carrier: the seat · the ui half, a domain:ui follow-up card: PercentCellRenderer (ui/packages/fields/src/index.tsx) reads percentDisplay on every host; the grid/detail relay sites that copy useGrouping onto the cell's field metadata copy it too; field.form.ts gains the row; liveness flips planned -> live and the describe drops its not-yet-read clause · dedupe words: percentDisplay, PercentCellRenderer plain percentage, percent progress bar" ] }