Skip to content

feat: a solution's declared dashboard is a tab beside it, and saas-ui says when someone last signed in - #993

Merged
antoinetoussaint-byte merged 3 commits into
mainfrom
feat/last-login-and-solution-dashboard-tab
Oct 2, 2026
Merged

antoinetoussaint-byte merged 3 commits into
mainfrom
feat/last-login-and-solution-dashboard-tab

Conversation

@antoinetoussaint-byte

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

Copy link
Copy Markdown
Contributor

Found in a QA pass of a solution on a hosted cell. The consumer half (a solution showing its sign-in time with <LastLogin>) follows once this is published.

Classification: feature, at the places that own it

  • Where a solution's declared dashboard sits is the host page's (s/[solutionId]/page.tsx). A solution cannot move it.
  • How a sign-in time is said is a SaaS-domain component, so it belongs in @codefly-dev/saas-ui.

1. The declared dashboard is a tab, not a block above the solution

QA saw a solution page that read, top to bottom: the dashboard's title, three widgets each saying "No data yet.", and only then the solution. The page rendered SolutionDashboards above SolutionRuntime, so a dashboard with nothing to show sat on top of what the person came for, on every visit.

  • Now: a solution whose data graph declares at least one dashboard gets the kit's tabs, App (default) and Dashboard. A graph of events and metrics with no dashboard gets no tab bar.
  • The App panel is keepMounted, so opening the dashboard does not unmount the remote, and an open conversation or a half-typed question survives the switch.
  • A solution with no dashboard renders exactly as before, with no tab bar for one tab.
  • Overlap with feat: group a solution dashboard into declared sections a viewer can arrange #958. Its section renderer fits inside the new Dashboard panel, and it does not touch page.tsx. It does conflict textually with this PR in module/docs/plugin-manifest-schema.md, the frontend package.json and package-lock.json, and both touch the kit manifests and ui-inventory.json. Whichever lands second reconciles them, keeping this PR's tab placement and feat: group a solution dashboard into declared sections a viewer can arrange #958's sections.

2. <LastLogin> in saas-ui

<LastLogin at subject? label? /> renders "Last login · 12 seconds ago".

  • It stays true as time passes. It re-says itself every second for the first minute, every 30 s up to an hour, then every 5 min. The next re-saying is scheduled from the time itself, and the clock is read through a ref, so a parent re-rendering often cannot hold it still.
  • It hydrates cleanly. The server render and the browser's first render both say the time in UTC (2026-10-02 10:00 UTC), which neither clock nor timezone can change. The relative words and the viewer's local time follow once mounted.
  • The exact time is on the <time dateTime title> and on the line beneath.
  • No recorded sign-in (null, or anything that is not a date) reads "Never / No sign-in is recorded yet", never "Invalid Date".
  • relativeTime(at, now, locale?) is the words alone, counting whole units elapsed through Intl.RelativeTimeFormat: 59.999 seconds is still "59 seconds ago".
  • Styling uses the kit's existing type slots (type-metric-label, type-metric-value-lg, type-caption-plain), with no new CSS.
  • Why it's the kit's: the host already has two hand-written "x ago" helpers that disagree (components/activity-feed.tsx and features/notifications/model/transforms.ts). Moving those onto relativeTime is not done here.

Kit version

The co-versioned kit moves to 0.11.4 (@codefly-dev/ui and @codefly-dev/saas-ui; CODEFLY_KIT_VERSION follows codefly-ui's package.json), and so do the root frontend package's pins and lockfile entries. node scripts/ci/kit-version.mjs check passes with full history.

Docs, inventory, stories

  • module/docs/plugin-manifest-schema.md: where a declared dashboard renders.
  • packages/saas-ui/README.md: a new Audit section.
  • ui-inventory.json: an entry for audit/last-login.tsx.
  • packages/saas-ui/stories/audit.stories.tsx: three stories on a fixed clock.

Review

A deep review of this head found four defects, all fixed in the latest commit, each with a test that fails on the previous code: the hydration mismatch, the timer held still by parent re-renders, the empty Dashboard tab, and units rounding up early. It also found the stale root pins and the wrong #958 claim above.

Tests

  • New s/[solutionId]/__tests__/page.test.tsx:
    • With a dashboard: tabs App and Dashboard. The dashboard is not shown until chosen, and the remote stays mounted once it is.
    • Without one: no tablist.
    • The first test fails on main's page.
  • New saas-ui/src/audit/__tests__/last-login.test.tsx: the words per unit; "now" for this second and for a clock ahead; live update under fake timers; "Never" for null and for a non-date.
  • Run:
    • npx vitest run --project pure, the whole project: 294 files, 3612 tests.
    • Host tsc --noEmit; saas-ui tsc and build.
    • scripts/check-boundaries.sh.
    • eslint on the changed files: no errors. One warning stays, the anonymous default export in the new stories file, which matches the existing datasources.stories.tsx.

CI note

The first run's Codefly quality failed fetching the vault agent, cannot download agent codefly.dev/vault:0.0.37 … unexpected status code 500. That is a download error, not this change.

Not verified

  • In a browser, or under external skins. The inventory entry says so, like its neighbours.

🤖 Generated with Claude Code

@antoinetoussaint-byte
antoinetoussaint-byte force-pushed the feat/last-login-and-solution-dashboard-tab branch from 6ec2181 to 6dd884b Compare October 2, 2026 10:32
… says when someone last signed in

The solution page stacked every declared dashboard above the solution, so a
dashboard with nothing to show yet sat on top of what the person came for and
pushed it below the fold. A solution that declares a dashboard now gets two
tabs, App and Dashboard; the App panel stays mounted while the dashboard is
open, so the solution keeps its state. A solution with no dashboard renders as
before.

saas-ui gains <LastLogin> and relativeTime: "Last login · 12 seconds ago",
kept true as time passes, the exact time on the <time> element and beneath,
"Never" for no recorded sign-in. The host already had two hand-written "x ago"
helpers that disagree; a solution no longer writes a third.

The co-versioned kit moves to 0.11.4 (@codefly-dev/ui, @codefly-dev/saas-ui).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
antoinetoussaint-byte and others added 2 commits October 2, 2026 11:30
… units; a graph with no dashboard gets no tab

From the review of this pull request:
- LastLogin's first render read the clock and the local timezone, so a
  server render and the browser's hydration disagreed and React recovered.
  The first render now says the time in UTC on both; the relative words and
  the viewer's local time follow once mounted.
- A parent re-rendering faster than the cadence restarted its interval every
  time (a new default clock function was a dependency), freezing "5 minutes
  ago". The clock is read through a ref and the next re-saying is scheduled
  from the time itself.
- relativeTime rounded before choosing a unit: 59.999 seconds read "1 minute
  ago". It now counts whole elapsed units.
- A data graph with events and metrics but no dashboard got a Dashboard tab
  over an empty panel. The tabs now need at least one declared dashboard.
- The root frontend package pinned the kit at 0.11.3; it pins 0.11.4.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
react-hooks refuses a ref written while rendering (Codefly quality lint). The
clock is now set in an effect declared before the schedule, so the first
reading already uses it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@antoinetoussaint-byte
antoinetoussaint-byte added this pull request to the merge queue Oct 2, 2026
Merged via the queue into main with commit 2e75241 Oct 2, 2026
24 checks passed
@antoinetoussaint-byte
antoinetoussaint-byte deleted the feat/last-login-and-solution-dashboard-tab branch October 25, 2026 17:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

model:xhigh lazybox: agent model tier xhigh (Astra)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant