Uh oh!
There was an error while loading. Please reload this page.
Align the manager dashboard with the sales deck's p20: overdue, list completeness, and the two charts - #111
Conversation
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
os-warren
commented
Sep 2, 2026
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 This PR lands after #107, by construction, and that is fine. The one red test is Two things worth having on the record:
The first simulation run rendering Generated by Claude Code |
os-warren
commented
Sep 2, 2026
Reviewed — merging, on the branch updated with |
Uh oh!
There was an error while loading. Please reload this page.
Fixes#109
Two KPI cards and two charts the deck's leadership first screen promises and
duly_duty_healthdid not have. All metadata — one new measure, one new dataset, four widgets, the zh-CN entries.duly_duty_health.tasks_overdue—late_after < {today} AND status IN (open, in_progress)duly_duty_register.approved_rate— approved duties over the register, byreview_statusduly_duty.form— duties, not tasksreview_statusis not onmainyet — one named red assertion, and the merge is already measuredThe completeness measures read
duly_duty.review_status, which #107 adds. It has not landed, sopnpm testreports exactly one finding and nothing else:validate,typecheckandbuildare all green —pnpm validatedoes not resolve dataset field references at all (objectstack#14105), sotest/metadata-bindings.test.tsis the only gate that can see this. Worth stating precisely, because the card predictedvalidatewould 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 at953bf9e:So this is purely a landing-order question: whichever of the two merges second merges
mainand re-runs. Nothing here is waiting on a decision.The measures are defined against
review_statuswith 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, theisMetricbranch, readsvalues[0]and drops every other selected measure. Silently: the query runs for all of them and the tile shows the first.widget.options.description— it has its own translation key ({ns}.dashboards.{dash}.widgets.{id}.subCaption), the server overlays it intranslateDashboard, andDashboardRenderer.tsxresolves it intWidgetSubCaption. That resolved value is attached only insidegetComponentSchema(), on the inlineobject-metric/ static-value arms. A dataset-bound widget renders throughDatasetWidget, which never reads it — anddatasetis required onDashboardWidgetSchema, 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_registerdeliberately does not declareduties_to_confirm/duties_to_reviewyet 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_dutyduly_duty_registeris the only dataset here that is not based onduly_task. That is not a convenience:test/datasets.test.tspins 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, notstatus == active. A paused duty is still owed — pausing is a scheduling state, not a withdrawal — and a retired one was withdrawn. Same boundarytasks_duedraws 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.sourcehere is the duty's own column.governed.tssays to readduly_task.sourcerather thanduty.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_overduecounts open governed work past its ownlate_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}overtasks_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:no widget rebuilds a grace-free lateness out of a due-date window(existing);the overdue count reads the dispatched deadline, never a due-date window(new) — assertslate_after: { $lt: '{today}' }, asserts the token is in the spec's own macro vocabulary, and asserts the measure's text does not containdue_dateat all.A task with no
late_afteris never counted, which is correct rather than a gap: no deadline was stamped, so there is nothing to be past — the same direction ascompleted_late: falsefor a task with no due date.本月工作构成: no month filter, and the palette is the field's
The pie counts
duly_duty.formover 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 != retiredalready says. Bolting on a date window would have to readeffective_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) andDatasetWidget'sbuildOptionColorMap/buildCategoryOrder:categoryColors, which take precedence over the positionalcolorspalette. SochartConfig.colorsrestatesduly_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;sortBy— so this widget authors nosortByat 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.showDataLabelsstaysfalse; 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_atanswers "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.tsgains 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 portRead live off
POST /api/v1/analytics/dataset/querybefore the screenshots, so the numbers on them are checkable: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_statusis refused loudly at query time, not silently answered as zero. A tile reading0on a governance rate is the one outcome that would ship wrong and look right.The same screen with
review_statuspresent. A local, uncommitted simulation of #107's field: the field added toduly_dutywithapprovedas the option default, then four duties moved off it through the REST boundary (2to_confirm, 1to_review, 1returned) the way #107's seed will. The mutation was confirmed on disk by anchoredgrep -ccounts before any reading was taken, and the script restored the tree from anEXIT/INT/TERMtrap —git statusclean after.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:
format: '0.00'was the right call, again. The first simulation run left every duty at theapproveddefault and the tile rendered1.00— precisely the case A dataset measure cannot render as a percent: the console's scale heuristic reports a rate of exactly 1 as1.0%#101 records'0.0%'getting wrong (the percent heuristicvalue > -1 && value < 1excludes exactly 1, so a fully approved register would print1.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_RECENTinsrc/data/demo-history.tsplaces them, and #52'slate_aftermakes them count. Verified against the fixture (4 open rows withlate_after < today) and again live (tasks_overdue → 4). Adding rows would have moved the countstest/seed.test.tspins for no gain. Thereview_statusvalues belong to #107's seed, per that card.Gates
At
f3047e8, the branch head, under the shared verify lock: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 ofduly_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 carryzh-CNentries, so #18's gate stays green.No changeset — this repo has no changeset mechanism (no
.changeset/, no dependency, no script, no mention inAGENTS.md). The four gates are the whole contract.Findings filed, not fixed here
🤖 Generated with Claude Code
https://claude.ai/code/session_01SqkTcrxUFci7nqXdbBSe2p