Skip to content

Align the manager dashboard with the sales deck's p20: overdue, list completeness, and the two charts - #111

Merged
os-warren merged 3 commits into
mainfrom
claude/issue-109-dashboard-p20
Sep 2, 2026
Merged

Align the manager dashboard with the sales deck's p20: overdue, list completeness, and the two charts#111
os-warren merged 3 commits into
mainfrom
claude/issue-109-dashboard-p20

Conversation

@os-warren

@os-warren os-warren commented Sep 2, 2026

Copy link
Copy Markdown
Collaborator

Fixes #109

Two KPI cards and two charts the deck's leadership first screen promises and duly_duty_health did not have. All metadata — one new measure, one new dataset, four widgets, the zh-CN entries.

p20 card before now
停滞项 ✔ three tiles unchanged
逾期项 duly_duty_health.tasks_overduelate_after < {today} AND status IN (open, in_progress)
按期完成率 ✔ (#52) unchanged
清单完备度 duly_duty_register.approved_rate — approved duties over the register, by review_status
各部门逾期项分布 horizontal bar, unit dimension, ordered by name
本月工作构成 pie over duly_duty.form — duties, not tasks

review_status is not on main yet — one named red assertion, and the merge is already measured

The completeness measures read duly_duty.review_status, which #107 adds. It has not landed, so pnpm test reports exactly one finding and nothing else:

dataset duly_duty_register · measures 'duties_approved'.filter key: "review_status"
  — "review_status" is not a field on duly_duty. Declared: business_unit, catalog_item, …

validate, typecheck and build are all greenpnpm validate does not resolve dataset field references at all (objectstack#14105), so test/metadata-bindings.test.ts is the only gate that can see this. Worth stating precisely, because the card predicted validate would be the one to refuse it.

Merged with #107's branch as it stands (868c8a5), everything is green — measured, not assumed, in a throwaway worktree at 953bf9e:

merge: 17 files changed, 985 insertions(+), 10 deletions(-)   no conflicts
pnpm validate   ✓ Validation passed (550ms)
pnpm typecheck  tsc --noEmit, no output
pnpm test       Test Files 30 passed (30)   Tests 787 passed (787)
pnpm build      ✓ Build complete (790ms)

So this is purely a landing-order question: whichever of the two merges second merges main and re-runs. Nothing here is waiting on a decision.

The measures are defined against review_status with no stand-in, no ?? fallback and no "temporarily count active duties instead" — a consumer-side accommodation for a field that is three days away is the shape that never gets removed.

The tile the platform cannot finish, and why it is not faked

The card asks the completeness tile for a sub-caption: 「N 条待确认 / M 条待审定」. A dataset-bound KPI tile can render no sub-caption at all today. Measured against this repo's objectui, not assumed:

  • plugin-dashboard/src/DatasetWidget.tsx, the isMetric branch, reads values[0] and drops every other selected measure. Silently: the query runs for all of them and the tile shows the first.
  • The authored sub-caption slot is widget.options.description — it has its own translation key ({ns}.dashboards.{dash}.widgets.{id}.subCaption), the server overlays it in translateDashboard, and DashboardRenderer.tsx resolves it in tWidgetSubCaption. That resolved value is attached only inside getComponentSchema(), on the inline object-metric / static-value arms. A dataset-bound widget renders through DatasetWidget, which never reads it — and dataset is required on DashboardWidgetSchema, so every spec-valid widget takes that path. Four layers of plumbing, wired to a slot no author can reach.

Filed as objectstack-ai/objectui#7293 with both measurements. The tile ships with the rate and its description line; the two pending counts are not approximated into the title, not written as static text that goes stale the first time somebody approves a duty, and not smuggled in as extra values[] that would not render. duly_duty_register deliberately does not declare duties_to_confirm / duties_to_review yet either — an unconsumed measure on a published contract is the thing ADR-0049 exists to remove. They arrive with the renderer.

This is the AGENTS.md rule 9 shape: the platform gap is reported where the next author will hit it (the dashboard's module header carries both citations), not written around.

Why a fourth dataset, and why it is based on duly_duty

duly_duty_register is the only dataset here that is not based on duly_task. That is not a convenience:

  • a standing duty never generates a task, so no task-based dataset can see one — the work-mix pie is the only place standing work appears on the manager screen at all;
  • an unapproved duty never dispatches, so it is absent from every task-based number without being absent from the obligation — which is exactly what a completeness rate is asked about.

test/datasets.test.ts pins the split (three are task-based and exactly one asks about the duties themselves), because a duty count and a task count that share a screen are two different populations and only their names look alike.

Two boundaries the dataset states at length rather than assuming:

  • status != retired, not status == active. A paused duty is still owed — pausing is a scheduling state, not a withdrawal — and a retired one was withdrawn. Same boundary tasks_due draws with != cancelled, one object up. Getting it wrong is invisible in the direction nobody checks: a rate over every duty ever declared falls every time a duty is retired, so the screen would report the register getting worse as it is tidied up.
  • source here is the duty's own column. governed.ts says to read duly_task.source rather than duty.source — that instruction is about a task-based measure keeping the caliber it was dispatched under. On a duty-based dataset there is no denormalised copy to prefer.

逾期项 is a plain date filter, and still not a due-date window

tasks_overdue counts open governed work past its own late_after — the deadline #52 stamps at dispatch from the duty's grace. A column against a date macro, so objectstack#14104 stays irrelevant rather than becoming a blocker again: there is no column-to-column comparison left to make.

The two-line version is due_date < {today} over tasks_due, and it is the grace-free approximation this file has refused twice already — it marks late every task still inside the grace its own duty grants, wrong in the direction a customer configures grace against. Both sides are now pinned:

  • widget side: no widget rebuilds a grace-free lateness out of a due-date window (existing);
  • measure side: the overdue count reads the dispatched deadline, never a due-date window (new) — asserts late_after: { $lt: '{today}' }, asserts the token is in the spec's own macro vocabulary, and asserts the measure's text does not contain due_date at all.

A task with no late_after is never counted, which is correct rather than a gap: no deadline was stamped, so there is nothing to be past — the same direction as completed_late: false for a task with no due date.

本月工作构成: no month filter, and the palette is the field's

The pie counts duly_duty.form over the register. Two decisions are written into the module header rather than left to be re-derived:

There is no "this month" window behind the deck's card name. A duty is a standing definition and has no month; what "this month's work" can honestly mean over duties is the register as it stands, which status != retired already says. Bolting on a date window would have to read effective_from / effective_to, which are null on almost every seeded duty — it would silently shrink the pie to whichever duties happen to carry an effective window and read as a composition of the whole register.

Slice colour and slice order come from the field, not from the widget — measured in objectui's core/src/utils/chart-presentation.ts (chartConfigPresentation) and DatasetWidget's buildOptionColorMap / buildCategoryOrder:

  • a select dimension's own option colours become categoryColors, which take precedence over the positional colors palette. So chartConfig.colors restates duly_duty.form's own three option colours — a different palette there would be inert decoration, and the contrast assertion over it would be testing a colour nobody sees;
  • slice order is the picklist's declared option order, which is the same count-independence the unit charts buy with sortBy — so this widget authors no sortBy at all, rather than adding a second, conflicting order.

All three inherited hexes were put through the file's existing WCAG 1.4.11 test before being restated: #2E7C8E (already here), #8C6512 (L≈0.149 → 5.3:1 on white, 3.6:1 on #0B0F14) and #576B73 (L≈0.138 → 5.6:1 and 3.4:1) — all inside the measured [0.118, 0.30] band, so the pie needs no per-theme colour either. showDataLabels stays false; the legend carries the names on the card background.

Six tiles is still four cards

Nothing was removed to hit a slide count. 停滞项 is deliberately three tiles — the >14d/>30d thresholds nest, so two separate tiles are the one arrangement that cannot be misread as a partition, and oldest_last_update_at answers "the worst one" with a date that ranks nobody. And neither existing chart was retired: "Not moving, by unit" and "Coming up" answer questions the deck's two do not.

test/dashboard.test.ts gains a small block pinning the four p20 numbers by the measure each binds rather than by widget id — the failure mode for a demo screen is not an error, it is a tile quietly missing, which reads as a screen that never promised it.

Browser verification — pnpm demo, fresh database, own port

Read live off POST /api/v1/analytics/dataset/query before the screenshots, so the numbers on them are checkable:

duly_duty_health  tasks_overdue                  → 4
  by business_unit → Northgate Operations 1 · Riverside Plant 1 · Northgate Quality 2
duly_duty_register duties_in_register by form    → Recurring 23 · One-off 1 · Standing 3

The screen as this PR ships it. Four p20 numbers, four charts. The completeness tile shows the analytics runtime's own 400 — which is the good failure: review_status is refused loudly at query time, not silently answered as zero. A tile reading 0 on a governance rate is the one outcome that would ship wrong and look right.

Duty health, as shipped

The same screen with review_status present. A local, uncommitted simulation of #107's field: the field added to duly_duty with approved as the option default, then four duties moved off it through the REST boundary (2 to_confirm, 1 to_review, 1 returned) the way #107's seed will. The mutation was confirmed on disk by anchored grep -c counts before any reading was taken, and the script restored the tree from an EXIT/INT/TERM trap — git status clean after.

governed duties fetched: 27
  Line safety walk — Riverside    -> to_confirm
  Toolbox talk record — Riverside -> to_confirm
  Waste transfer log review …     -> to_review
  Emissions return — Northgate    -> returned

{"duties_approved": 23, "duties_in_register": 27, "approved_rate": 0.8518518518518519}

Duty health with review_status

List completeness tile

Screenshots live on a throwaway branch (claude/issue-109-shots, an orphan commit holding three PNGs) so no binaries enter this diff.

Two things the screenshots confirm that a unit test cannot:

  • the unit order is the org chart's, visibly. Northgate Operations (1), Northgate Quality (2), Riverside Plant (1) — the largest bar sits in the middle. Ordered by the count it would be first.
  • format: '0.00' was the right call, again. The first simulation run left every duty at the approved default and the tile rendered 1.00 — precisely the case A dataset measure cannot render as a percent: the console's scale heuristic reports a rate of exactly 1 as 1.0% #101 records '0.0%' getting wrong (the percent heuristic value > -1 && value < 1 excludes exactly 1, so a fully approved register would print 1.0%). Measured by accident, and it is the number a customer most wants to see.

Seed

No seed change was needed and none was made. The card asks for "a few late tasks"; the existing fixture already produces four, across three business units — LATE_MOST_RECENT in src/data/demo-history.ts places them, and #52's late_after makes them count. Verified against the fixture (4 open rows with late_after < today) and again live (tasks_overdue → 4). Adding rows would have moved the counts test/seed.test.ts pins for no gain. The review_status values belong to #107's seed, per that card.

Gates

At f3047e8, the branch head, under the shared verify lock:

pnpm validate   ✓ Validation passed (545ms)   (one expected warning: hierarchy-security, per AGENTS.md)
pnpm typecheck  tsc --noEmit, no output
pnpm test       Test Files 1 failed | 28 passed (29)   Tests 1 failed | 753 passed (754)
                └─ the single `review_status` reference above, nothing else
pnpm build      ✓ Build complete (723ms) — dist/objectstack.json (179.2 KB)

And green across all four on the merged tree at 953bf9e, above.

test/i18n-coverage.test.ts's untranslatable count moves 29 → 36: duly_duty_health.Overdue (+1) and the whole of duly_duty_register — label, description, one dimension label, three measure labels (+6). Same declared gap, seven strings wider; it closes for all of them at once when the platform makes datasets translatable (#106 / objectstack#14253). New widget titles and descriptions all carry zh-CN entries, so #18's gate stays green.

No changeset — this repo has no changeset mechanism (no .changeset/, no dependency, no script, no mention in AGENTS.md). The four gates are the whole contract.

Findings filed, not fixed here


🤖 Generated with Claude Code

https://claude.ai/code/session_01SqkTcrxUFci7nqXdbBSe2p

os-warren and others added 2 commits September 2, 2026 04:36
The deck's leadership first screen asks for four KPI cards and two charts.
Two of the four cards and both charts were missing:

- `duly_duty_health.tasks_overdue` — open governed work past its own
  `late_after`, the deadline #52 stamps at dispatch from each duty's grace.
  A column against a date macro, not a column against a column, and NOT the
  `due_date < {today}` window that marks late every task still inside the
  grace its duty granted.
- `duly_duty_register` — a fourth dataset, and the only one based on
  `duly_duty`. A task-based dataset structurally cannot see a standing duty
  (it never generates a task) or an unapproved one (it never dispatches),
  which is exactly what the two remaining cards ask about. It counts
  completeness over `review_status`, never over how many items a list has.
- Four widgets: the Overdue tile, the List completeness tile, Overdue by
  business unit (ordered by the unit name, never the count) and Work mix (a
  pie over `duly_duty.form`, the one place standing work is visible).

The completeness tile's "N to confirm / M to approve" sub-caption is
deliberately absent: a dataset-bound tile renders `values[0]` and drops the
rest, and the authored sub-caption slot is read only on the inline metric
path, which no spec-valid widget can reach. Measured on objectui and filed
upstream rather than faked as static text.

`review_status` is added by the parallel duty-review card, so
`test/metadata-bindings.test.ts` reports exactly one unresolved reference
until that lands; validate, typecheck and build are green.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SqkTcrxUFci7nqXdbBSe2p
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SqkTcrxUFci7nqXdbBSe2p

Copy link
Copy Markdown
Collaborator Author

Open question: A — keep all four charts. Landing order: after #107.

A. Nothing on the card asked for a removal, and both pre-existing charts carry measured reasoning in the module header this task was told to extend, not contradict — the not-moving-by-unit chart is the earliest actionable per-unit signal and the forward look is the deck's own p18 point. The deck's "four cards, two charts" describes the slide's content, not a cap on the screen. If the demo ever needs the first screenful to be a literal p20, that is option C — two layout.y values — and it deletes nothing. Not doing it pre-emptively.

This PR lands after #107, by construction, and that is fine. The one red test is duly_duty_register.duties_approved filtering on review_status, which exists only on #107's branch. You measured the merge (#107 head 868c8a5 into yours: no conflicts, all four gates green, 787 tests) rather than asserting it, so the dependency is established as landing order and nothing else. I will merge #107 first, then update this branch from main and re-gate before merging.

Two things worth having on the record:

  • pnpm validate did not refuse the dangling review_status filter — my card predicted it would. Measured: validate does not resolve dataset field references at all (objectstack#14105, already filed), so the app's own test/metadata-bindings.test.ts was the only gate that saw it. Another instance of the guard we wrote ourselves being the one that fires.
  • The completeness tile ships without its "N to confirm / M to approve" sub-caption, and that is the right call. A dataset-bound KPI renders values[0] and drops the rest, and the authored sub-caption slot only reaches the inline-metric path no spec-valid widget can take. Filing objectui#7293 and not faking it as static text is exactly the discipline — a static "0 to confirm" on a demo screen would have been a lie with digits.

The first simulation run rendering 1.00 because every duty sat at the approved default, and thereby re-confirming #101 ('0.0%' would print 1.0% on a perfect register) — that is a free second measurement of an open finding, and worth the sentence it got in the PR.


Generated by Claude Code

@os-warren
os-warren marked this pull request as ready for review September 2, 2026 05:28

Copy link
Copy Markdown
Collaborator Author

Reviewed — merging, on the branch updated with main (c915f82).

#107 landed (a672ca3e), so review_status exists and the one red test from the report is gone. I merged current main into this branch locally, re-ran everything on that tree, and pushed the merge so the PR's head is the tree I gated:

validate 0 · typecheck 0 · test 0 (Test Files 32 passed, Tests 833 passed) · build 0. No conflicts.

Design rules re-checked in the source rather than taken from the report: overdue is late_after < {today} over open governed work with no due_date window; the by-unit bar is ordered by unit name; completeness is duties_approved / duties_in_register over review_status, never a count of items per person; the work-mix pie counts duly_duty.form (duties, not tasks) in picklist order with no sortBy; no owner dimension anywhere on the screen. Open question answered above: A, keep all four charts.

For the record: the suite now prints 73 WARN option visibleWhen … failed to evaluate — allowed through lines, from the owner gate on review_status under system writes. That is objectstack#14416, filed from #107 — noise, not a finding here, and worth knowing it is expected before anyone chases it.


Generated by Claude Code

@os-warren
os-warren merged commit 8a0a436 into main Sep 2, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

仪表盘对齐方案 p20:逾期项、清单完备度、各部门逾期分布、本月工作构成

1 participant