Uh oh!
There was an error while loading. Please reload this page.
Manager dashboard: what has stopped, and what is coming up - #67
Merged
Conversation
Adds `duly_duty_health`, bound entirely to the semantic layer, and reaches it from the Team nav group. Two of the card's five items — "Late" and "On-time rate" — are the same missing comparison (`due_date + duty.grace_days`, objectstack#14104) and are deliberately not approximated; the product decision is open on #52. The absence is stated on the screen rather than left silent. `test/dashboard.test.ts` resolves every widget binding against the datasets barrel: nothing in the platform does, and an empty "not moving" tile reads exactly like a healthy team. `test/metadata-bindings.test.ts` is taught the `dashboard` nav type it deliberately fails on. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SqkTcrxUFci7nqXdbBSe2p
The card and its PM note assumed the whole dashboard binding surface was
unguarded, carrying #14105 (datasets) up a layer. Measured instead, one
mutation at a time on cli 17.2.0, and most of it IS guarded: widget
dataset / dimensions / values and filter {tokens} all fail both gates with
a named rule, and an unresolvable nav dashboardName is refused by
defineStack itself.
Two references are not resolved and fail silently — a widget's own filter
KEYS, and options.sortBy naming something the widget does not select.
Filed as objectstack#14148, with the note that the identical resolution
already exists one key over (dashboard-filter-field-unknown, #3365).
Comments that claimed otherwise are rewritten; the stopgap now points at
the gap it actually stands in for.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SqkTcrxUFci7nqXdbBSe2pos-warren
marked this pull request as ready for review
September 1, 2026 08:25
Uh oh!
There was an error while loading. Please reload this page.
This was referenced Sep 1, 2026
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 freeto 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#10
duly_duty_health, bound entirely to the semantic layer, reachable from the Team nav group as its first entry.Gates green on
2d1a27e(the tip of this branch):pnpm validate0 ·pnpm typecheck0 ·pnpm test0 (457 passed / 15 files, up from 419 / 14) ·pnpm build0. The onevalidatewarning is the expectedhierarchy-securitycapability-provider line, which AGENTS.md rule 7 says not to silence.The layout, and the item that is not in it
not_moving_14d(top-left, largest tile) +not_moving_30d, plusoldest_touchas a datenot_moving_by_unit, ordered by the unit dimensioncoming_up, due next 14 days by weekItems 2 and 3 are the same missing comparison, not two omissions. Both need
due_date + duty.grace_days: there is no date arithmetic in the filter grammar, the{N_days_ago}vocabulary is relative to NOW rather than to another column, and here the offset is itself a column (objectstack#14104). No dataset declares an on-time or a lateness measure for a widget to bind, andsrc/datasets/is not mine to change on this card.A grace-free lateness is expressible without touching a dataset — a widget
filterofdue_date < {today}overtasks_dueis two lines — and is deliberately not built. It marks late every task still inside the grace its own duty grants, so a customer who configured 7 days gets its people listed late the morning after the due date. That is the bug #48 already records against thelateLIST view, and the product decision is open on #52. Deciding it in a widget would settle it by accident.test/dashboard.test.tspins the absence in both directions: no widget may bind anon_time/late/overduemeasure, and no widget may rebuild one out of a due-date window bounded above by{today}or a past token (the forward window this dashboard does use is bounded above by a future token, which is a different question). When the tile arrives, both pins come off with it.The absence is also stated on the screen, in the dashboard description, next to the caliber note — a manager reading "not moving: 3" on a screen silent about lateness concludes there are three problems. An unexplained absence is a wrong number with no digits.
What the platform actually guards — the card's second assumption was wrong too
The PM note said dataset bindings are guarded "above" and asked whether
test/metadata-bindings.test.tsreaches dashboard widgets. It does not — itsdulyDashboardsimport (added here) resolves the nav entry only. But the more useful answer came from measuring rather than assuming. Each mutation below was applied to the real dashboard, confirmed on disk withgrep -Fbefore the gates ran, and restored by a trap:validatebuilddataset→duly_stagnatoinwidget-dataset-unknown(+ did-you-mean)dimensions[]→business_unittwidget-dimension-unknownvalues[]→untouched_over_14widget-measure-unknown{token}in widget filter →{14_days_hence}filter-token-unknown, path-precisedateRange.field→nonexistent_columndashboard-filter-field-unknowndashboardName→duly_ghostdefineStackcross-referencefilterKEY →due_daetoptions.sortBy→not_selectedThe last two are the real holes, and the first of them is sharp: on the same filter node, a bad
{token}is caught with the exact pathwidgets[4].filter.due_date.$ltewhile a misspelt column is not — anddashboard-filter-field-unknownalready resolves dataset → base object → field set per widget for the dashboard-level filter one key over. Filed upstream as objectstack-ai/objectstack#14148 with both halves and the repro.test/dashboard.test.ts's binding walk is the repo-local stopgap and is written to be deleted when that lands.Every claim in the checked-in comments is one of these measurements; the first commit's comments asserted the opposite (inherited from the card) and the second commit corrects them.
Shapes the PM flagged, and where each one landed
>14dand>30dare separate tiles, never two series in one chart. Pinned: no widget may select two nested thresholds together, none may draw one as a pie/donut/funnel, and none may stack one.due_week/due_month—coming_upgroups bydue_weekonly.tasks_dueidentical in both datasets — so the forward look is scoped by a date window and not narrowed by status: addingstatus IN (open,in_progress)would put a different number behind a name the semantic layer defines once.oldest_last_update_atis a timestamp — a metric tile labelled as a date, never a bar length.No ranking of people
No widget selects
owner. The rule is pinned as a property rather than a spot-check: a person dimension is detected by resolving the dataset dimension's field to asys_userlookup, so a second one added later is covered without editing the test. Two further pins hold even if a per-person workload widget is ever added deliberately (duly_workloadkeepsownerfor exactly that): it may not be ordered by a measure, and it may not be truncated to a top N.sortByon the unit chart names the dimension. Ordering a lookup used to sort by the opaque FK id — "sorted by unit" that reads as random (objectstack#3680) — which was fixed upstream in #3693 to sort by the resolved display label. That is what makes this the unit name order the card asked for, not merely a count-free one.Contrast, both themes
No text is drawn on a fill anywhere:
showDataLabels: falseis stated explicitly on both charts, so every label renders as axis or legend text on the card background. That is not tidiness — it is forced. Both fills are mid-tones chosen to clear 3:1 as graphical objects (WCAG 1.4.11) against a white and a near-black card, since metadata carries no per-theme colour:#B07C17(L≈0.237 → 3.7:1 light, 5.3:1 dark) and#2E7C8E(L≈0.169 → 4.8:1 light, 4.0:1 dark). At that lightness white label text on the amber fill is 3.7:1, a failure for text — the two constraints have no common solution in one hex, so the labels come off the fill. The app's darker palette entries were measured and rejected for fills on the same test (#16515F2.2:1,#5A3F0C2.0:1 on a dark card). Tiles usewarning/orangeand neverdanger: attention, not blame — asserted.File surface — one breach, flagged
test/metadata-bindings.test.tsis outside the surface the card named, and the change is not optional: that file's two tripwires fail the moment adashboardnav entry exists ("teach walkNav about it before trusting the nav check"), which is the guard working as designed. It is taught the type —dashboardNameresolved against the dashboards barrel, with self-test fixtures in both directions — and its nav-type tripwire now excludes the type it reads. Its comment records that this one, unlikeviewName(#14108), is not an unguarded reference:defineStackrefuses it. Everything else is on-surface:src/dashboards/*, the Team nav entry insrc/apps/duly.app.ts,test/dashboard.test.ts.objectstack.config.ts,src/datasets/andsrc/views/are untouched.What to look at in the browser
Acceptance is a running app, so the three things I could not verify from here:
oldest_touch— aminover adatetime. It should read as a date; if the KPI card renders a raw ISO string or an epoch number, that is a renderer formatting gap and I would rather file it than paper over it with an unverifiedoptionskey.coming_upbucket boundaries — ISO weeks (Monday start) wherever the driver does not truncate server-side; the window is[today, +14d], so the first and last buckets are partial weeks by construction.In this open-edition checkout the hierarchy scopes resolve to owner-only (AGENTS.md rule 7), so every number will be scoped to the signed-in user rather than to their unit — that is the edition, not this dashboard.
Generated by Claude Code