Skip to content

fix(dashboard): additive layout controls, compact defaults and stable ordering - #1045

Draft
antoinetoussaint-byte wants to merge 2 commits into
mainfrom
issue-1044-a-solution-s-dashboard-cannot-express-layout-and
Draft

antoinetoussaint-byte wants to merge 2 commits into
mainfrom
issue-1044-a-solution-s-dashboard-cannot-express-layout-and

Conversation

@antoinetoussaint-byte

@antoinetoussaint-byte antoinetoussaint-byte commented Oct 8, 2026 •

Copy link
Copy Markdown
Contributor

Closes #1044. Includes the existing metric-tier implementation from #1043 / #1041 (commit 71b8749), reused after a coordination comment rather than implemented again. This is one self-contained PR against main; do not merge or deploy as part of this task.

A declaration with four scalar widgets followed by five series now renders compact scalar cards above half-width desktop charts, with single-column mobile layout. Short cards retain their natural height. The sortable host view uses the same kit geometry as a standalone dashboard.

Classification and ownership

  • Fix at the owner: additive presentation schema and validation in the manifest package; matching SDK types and propagation through runDashboard; fromDashboardData carries sections, columns and spans to the kit.
  • Fix at the owner: the kit owns responsive layout and metric rendering. The host supplies per-widget queries, viewer preferences and provenance controls. Sections use the kit's Section, scalars use StatTile, and charts use the metric tier reused from Draw every dashboard with the kit's metric tier, and refuse re-implementing it (#1041) #1043.
  • Fix at the owner: saved-layout reconciliation is in the host preference parser; drag geometry and section admission are in SortableGrid. Widget identity stays stable across band changes, and keyboard focus follows the widget ID.
  • Existing labelled stopgap, unchanged in scope: preferences remain in browser storage (the STOPGAP comment in useDashboardLayout names the missing durable settings field). They do not follow a viewer across devices. No new workaround or issue was introduced.

Additive contract and rollout

Dashboard.columns, MetricWidget.span and Dashboard.sections are optional. Sections have id, title, optional description and columns; widgets refer to them with optional section. Column counts and spans are integers 1–4. Stack ignores sizing hints. Ungrouped widgets appear first, then sections in declared section order; widget order is preserved within each. Preferences reorder within a declared section and cannot change membership.

All schema additions are additive: existing declarations continue working untouched. This validator ignores future unknown presentation fields on dashboards, widgets and sections, while metric/query validation stays strict. Already-released hosts used exact-key validation and will reject these new fields. Upgrade those hosts before sending the fields, or keep sending the legacy declaration. This PR cannot retroactively make old binaries ignore fields.

#958 already proposes the same sections / widget section shape. This PR follows that vocabulary and adds column counts and spans, but does not include its broader section editing, metric-format or time-bucket changes. The overlapping section implementation in #958 will need reconciliation; it must not be merged independently without that review.

Reordering diagnosis

Saved layouts were already keyed by widget ID, with a seen list, not by position. A reproduced three-to-nine-widget failure was an unchanged saved default: the parser appended new IDs after the three old tiles, interleaving newly declared scalars with old charts. An unchanged saved default now follows the current declaration. Real custom orders retain surviving IDs, append new IDs once and preserve intentional removals. The affected browser's actual saved value was unavailable, so this is a reproduced failure mode, not a claim that its historical storage was inspected.

The nine-widget regression fixture is serialized directly from the supplied consumer declaration under a generic filename (platform-signins.json), with all three platform events, nine metrics, the derived ratio and count_distinct over actor_id preserved. Tests cover untouched order, saved three-to-nine growth, customized order/removal, renaming/reordering of declarations, section geometry, scalar and series spans, keyboard focus, and rejected cross-section drops.

Documentation and generated artifacts

Behavior docs updated: module/docs/plugin-manifest-schema.md, module/AUDIT_METRICS.md, module/services/frontend/AGENTS.md, and packages/codefly-ui/README.md.

Before layout edits, prepare:frontend plus preview stylesheet regeneration returned a clean tree. The complete generated preview stylesheet is retained, as is Next's next-env.d.ts emitted by native CI type generation (production paths). No generated output was hand-edited or selectively retained. Published versions move together: UI kits 0.12.7, SDK 0.3.18, manifest 0.1.2; npm regenerated the lockfile.

Verification

  • TypeScript and all package builds passed.
  • Full pure suite passed: 317 files, 4,106 tests; the subsequently added cross-section drop regression also passed with all 12 sortable-grid tests.
  • Supplied consumer's existing go test dashboard.go graph_test.go passed.
  • Chromium rendered the kit with the supplied nine-widget shape at 1280, 768 and 390px: four 90px-high scalar cards share the desktop row; series begin below; no horizontal overflow. A separate section/span case measured a short card at 158px beside a 299px chart, proving it does not inherit its neighbour's height. These are kit previews with synthetic series, not live composition evidence.
  • Naming, repository boundaries, kit-version and formatting checks passed.
  • Native codefly ci run --changed-file module/services/frontend/code/src/solutions/SolutionDashboard.tsx --temporary-ports was attempted twice. The first run reached 4,107 passing tests and exposed four assertions about UI co-versioning/shared versions and the section primitive; those were fixed, and the subsequent full pure suite passed. The final native rerun was terminated with exit code 137 before emitting a report. Its log does not establish the cause. The complete native gate, dependency audit, SBOM and container build remain unverified. GitHub checks are running on this draft PR.

Not verified: a newly registered live consumer composition, production deployment, publication, or every service's full gate. Nothing was merged or deployed.

antoinetoussaint-byte and others added 2 commits October 8, 2026 17:33
…refuse re-implementing it (#1041)

`@codefly-dev/ui/dashboard` shipped two tiers. The host's own operations pages
used the metric one; a solution's dashboard got the older basic one — twice
over, because `src/solutions/SolutionDashboard.tsx` carried its own copy of the
widget-to-chart dispatch, the responsive grid, the card shell and the empty
state beside the kit's `<Dashboard>`, and both copies drew with `charts.tsx`.
So the same declaration rendered plainer inside the host than beside it, and
there were two renderers of one thing to keep in step.

**A fix at the owner.** `<Dashboard>` now paints with the metric tier, contract
unchanged (a resolved `DashboardView` in; no fetching, no host context, no app
imports). A scalar widget is a `StatTile` — the series total in the widget's
format and unit, a sparkline and a delta against the previous bucket when the
metric is bucketed over time, and a `MetricStateBadge` from `series.coverage`.
A series widget is the kit's metric line/area/bar chart over a `ChartSeries`,
which brings the axes, the crosshair readout, the legend and the visually-hidden
data table that is the chart's screen-reader representation.

A withheld total renders as a dash and never as `0`: `Metric.value` now admits
`null` so that is a property of the primitive rather than of a state mapping,
and a new `no_total` state says which of partial / empty / no-total withheld it.
A series with no points reads as no data, not as a zero, because the aggregate
omits a bucket instead of emitting an empty one. A sparkline and a bucket delta
are drawn only for a time-bucketed series — a line through counts grouped by
event type would assert an order the data does not have.

`SolutionDashboard.tsx` now mounts that renderer and draws nothing itself. It
keeps exactly what is the host's, composed through four new presentational
slots: the + menu as header `actions`, the drag-to-reorder `SortableGrid` as
`layout`, the per-widget audit query as `renderWidget`, and its own `empty`
line. The queries stay one per widget — a widget whose metric errors still
fails alone — which is why the kit also exports `DashboardWidget`, the same
renderer for one widget: React fixes the number of hooks a component may call,
so a varying set of per-widget queries cannot live in a renderer that takes the
whole view. Its 16 existing tests still pass; two assertions and one helper
moved onto the markup the metric tier produces, each with the reason in place.

**A gate**, in the shape the kit's other consumer already uses:
`src/components/__tests__/kit.test.ts`. A declaration taking a name
`@codefly-dev/ui` exports must render the kit's component of that name or carry
a reviewed entry saying what the two things are; `class-variance-authority` is
no longer a dependency of this app (nothing imported it), so a kit-shaped
primitive cannot be built here at all; and the two chart surfaces stay
re-exports. Module-local declarations count, not only exported ones — that is
how both duplicates in this tree arrived, and a gate matching only exports
walked past both. Nine fixtures cover must-catch, must-not-catch, and the same
duplication reached another way.

Docs: `module/AUDIT_METRICS.md` gains "How a resolved series is drawn" — the
tier, and a table of what each widget and each coverage state renders, which is
what downstream consumers were guessing at; `module/services/frontend/AGENTS.md`
says what the solution dashboard owns and what the kit draws; the kit's README
names the tier and the slots.

Refs #1041

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@antoinetoussaint-byte

antoinetoussaint-byte commented Oct 19, 2026 •

Copy link
Copy Markdown
Contributor Author

A lazybox agent is working on this. codex (Astra · high) started at 2026-11-04 03:12 UTC, last renewed the claim at 2026-11-04 10:02 UTC, and the claim lapses at 2026-11-04 11:02 UTC unless it is renewed again. A claim is an assertion with an expiry, not proof that a process is alive.

claim detail
Lazybox-Claim-Device: effacd542b611010656e
Lazybox-Claim-Session: 4966ec75b6
Lazybox-Claim-Workspace: github-codefly-dev-module-saas-starter-1045
Lazybox-Claim-Agent: codex
Lazybox-Claim-Model: Astra · high
Lazybox-Claim-Started: 2026-11-04T03:12:40Z
Lazybox-Claim-Heartbeat: 2026-11-04T10:02:18Z
Lazybox-Claim-Expires: 2026-11-04T11:02:18Z

@antoinetoussaint-byte antoinetoussaint-byte added working Claimed by a lazybox agent; cleared when the session ends. and removed working Claimed by a lazybox agent; cleared when the session ends. labels Nov 4, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

working Claimed by a lazybox agent; cleared when the session ends.

Projects

None yet

1 participant