Skip to content

F5: Dashboard — landing screen with today's numbers (#41) - #43

Merged
mforce merged 2 commits into
mainfrom
feat/f5-dashboard
Jul 16, 2026
Merged

mforce merged 2 commits into
mainfrom
feat/f5-dashboard

Conversation

@mforce

@mforce mforce commented Jul 16, 2026

Copy link
Copy Markdown
Owner

Closes #41. First Phase 1.1 slice (epic #14).

What

Replaces the placeholder Dashboard with a real landing page:

  • Today — per-flock entry status / eggs / losses / mortality; flocks without an entry today get a highlighted no entry flag.
  • Stock — available + restricted by grade, total available.
  • Recent sales — last 5 orders (ref, customer, status, formatted total).

Each panel heading links to its full screen.

Decisions

  • Client-side composition, no new backend — 5 parallel GETs against existing endpoints. The issue left an aggregate GET /dashboard endpoint as an option if composition got heavy; it didn't (single Promise.all, one render).
  • todayIso() extracted to web/src/lib/dates.ts (shared with DailyEntryPage; same farm-local-date caveat, issue Use farm-local dates for withdrawal restriction and allocation boundaries #35).
  • Adds .claude/skills/verify/SKILL.md — documented local runtime-verification recipe.

Verification (chromium, real stack: dev DB + API + Vite)

  • Login lands on dashboard; all three panels render live data (screenshot in issue-ready state).
  • Flock without today's entry shows the no entry flag; flock with a Submitted entry shows its numbers.
  • Panel heading links navigate (dashboard → stock → back).
  • Aborting one of the five fetches shows the dashboard error state instead of a broken page.
  • No console errors on warm loads.

mforce added 2 commits July 16, 2026 10:18
Replaces the placeholder Dashboard with three panels composed
client-side from existing read endpoints (5 parallel GETs — no new
backend, per the issue's decision point):

- Today: per-flock entry status, eggs, losses, mortality; flocks with
  no entry yet are flagged "no entry" so a missed capture is visible
  at a glance.
- Stock: available/restricted by grade with total available.
- Recent sales: last 5 orders with ref, customer, status, total.

Each panel heading links to its full screen. todayIso() moves to a
shared web/src/lib/dates.ts (was private to DailyEntryPage; same
farm-local-date caveat, issue #35).

Also adds .claude/skills/verify/SKILL.md documenting the local
runtime-verification recipe (dev DB + API + Vite + chromium driving).

Verified end-to-end in chromium: login lands on dashboard, panels
render live data, no-entry flag appears for a flock without today's
entry, panel links navigate, and a failed dashboard fetch shows the
error state.

Closes #41
- Panels degrade independently (Promise.allSettled): one failed fetch
  blanks its own panel with "Could not load." instead of the whole
  page; full-page error only when every fetch fails, and it surfaces
  the ApiError message instead of a generic hint. (pi High/Med)
- Only Active flocks get the "no entry" missed-capture flag;
  depleted/archived flocks stay visible only when they have an entry
  for the day. (codex P2)
- Explicit limit=500 (the server clamp) on flocks/customers/entries
  lookups instead of the silent 100-row default page; listFlocks and
  listCustomers wrappers gain an optional limit param. (codex P2,
  pi Med)
- Query date captured once at mount so the header and the queried
  "today" can't diverge across a midnight rollover. (pi Low)

Re-verified in chromium: happy path renders all panels; aborting
/api/v1/stock blanks only the Stock panel while Today and Recent
sales stay live.
@mforce

mforce commented Jul 16, 2026

Copy link
Copy Markdown
Owner Author

Review round: codex + pi

# Source Severity Finding Action
1 codex P2 Inactive (Depleted/Archived) flocks flagged "no entry" — implies missed capture that isn't one Fixed — only Active flocks get the flag; inactive flocks shown only if they have an entry today (74454f6)
2 codex + pi P2/Med Default page size 100 silently truncates flocks/customers (and entries capped at 200) — wrong "no entry" flags, UUID prefixes for customer names Fixed — explicit limit=500 (server clamp max) on all three lookups; wrappers gained optional limit param. Past 500 rows the tail still drops — noted in code, real paging when a farm gets there (74454f6)
3 pi High Promise.all fail-fast: one failed GET blanked the entire dashboard Fixed — allSettled, per-panel degradation; full-page error only when all five fail (74454f6)
4 pi Med Error handler discarded ApiError; auth/tenant failures reported as "Is the API up?" Fixed — global error surfaces the ApiError message (74454f6)
5 pi Low Header todayIso() re-evaluated per render vs query date captured at mount — divergence across midnight Fixed — date captured once in state (74454f6)
6 pi Low No abort/ignore-flag on unmount Deferred — matches every other screen's pattern; harmless in React 18 (state set on unmounted component is a no-op), sweep all screens together if it ever matters
7 pi Low No background/refocus refresh — data stale while page stays open Deferred — no screen polls in the MVP; revisit as a cross-cutting concern
8 pi Low find per row O(n·m) Deferred — MVP sizes; same idiom as History/Stock pages
9 pi Low Positional destructuring across 5 promises fragile to reorder Skipped — inherent to Promise.all/allSettled use; names are adjacent to positions

Re-verified at runtime after fixes: happy path renders all three panels; aborting /api/v1/stock blanks only the Stock panel while Today and Recent sales stay live; no page errors.

@mforce
mforce merged commit 6b619da into main Jul 16, 2026
3 checks passed
@mforce
mforce deleted the feat/f5-dashboard branch July 26, 2026 02:25
mforce added a commit that referenced this pull request Aug 31, 2026
…shboard ports (#623)

* docs(aspire): record the second local database and pin the AppHost dashboard ports

Aspire starts its own PostgreSQL on its own volume with a generated password
held in the AppHost's user-secrets, so it is a second database rather than a
view onto the Compose dev stack. Any run-then-exit verb started by hand is
outside the AppHost's process graph and silently addresses Compose instead,
which is how bootstrap-admin failed twice with two unrelated-looking messages
before anyone suspected the database.

Records that as decision 565 and threads it through the places someone would
actually be standing when they hit it: AGENTS.md, CONTRIBUTING.md, the verify
skill's pre-flight, deploy/README.md, the break-glass runbook, and a fourth
form in the first-admin runbook for the Aspire stack.

Also pins the AppHost's own endpoints, which are not resources and so cannot be
reached by LocalPorts: dashboard 18888, OTLP 18889, resource service 18890. All
three are plaintext loopback, so the profile sets ASPIRE_ALLOW_UNSECURED_TRANSPORT
and the guard asserts that flag beside the http scheme it exists for; moving the
URLs to https is what retires it. Only the host and port are stable, the
dashboard's login token is still minted per run.

Keeping the committed LocalPorts clear of 5432 and 6379 is load-bearing rather
than cosmetic: ConnectionStrings:Default is a single value naming localhost:5432,
so letting both stacks hold that port makes it mean "whichever stack is up" for
every hand-run verb and IDE debug session. Disjoint ports keep the mistake loud.

The .gitignore carve-out states an intent that was previously an accident.
verify/ has been tracked since #43 while .claude/skills arrived in #462, and
gitignore does not apply to tracked files, so the skill showed up in every diff
looking unintentional.

Verified: dotnet build clean, AppHost.Tests 10/10, Domain 361, Application 175.
The launch-profile guard was mutation-checked - dropping applicationUrl,
drifting the OTLP port, and switching the dashboard to https while leaving the
unsecured-transport flag set each turn it red, and it passes again on restore.
Integration tests and the SPA suite were not run; this diff touches neither.

* docs(aspire): name the silent wrong-database success and fix AppHost detection

CodeRabbit review on #623. All five findings were confirmed against the files
before anything changed; the first is a factual error rather than an omission.

The form 3 troubleshooting claimed running it against a live Aspire stack
"surfaces as a connection error, never as anything about the database being
empty". That holds only when Compose is down. With both stacks up - the normal
state on a machine that uses either - form 3 reaches Compose and exits 0. Two
shapes, both silent: Compose had no Owner, so it provisions one there and hands
you a password for the wrong database; or Compose already had one and it prints
"Admin already provisioned ... nothing to do", which reads as success while the
Aspire database still has no Owner. Verified against BootstrapAdminCliCommand,
which returns 0 on both paths. The failure table said the other stack holding
the port produces a connection failure - holding the port is exactly what makes
it connect instead.

The verify skill detected a running AppHost with docker ps piped through
grep -E 'postgres-|redis-', which also matches deploy-redis-1 and
cluckwork-sim-redis-1, so Compose and the sim stack both read as Aspire. It
also treated the LocalPorts defaults as fixed, though they are overridable and
can be empty for a random port. Now detects the AppHost process, its pinned
dashboard port, and aspire describe for the endpoints a given run actually
holds.

The same file told Aspire users to read Seed:* from the API's user-secrets,
which are the Compose credentials. Split by stack, and separated the two
passwords people conflate: Parameters:postgres-password in the AppHost's
user-secrets is the database credential form 4 passes, never a login. The
sign-in password is bootstrap-admin's stdout on the run that created the Owner,
unrecoverable afterwards, so recover-admin rather than a hunt.

Also a language identifier on one fence for MD040, and the drill's reset command
had a literal ellipsis where the second docker compose invocation belongs, so
copying it never restarted the stack.

Swept for the same three defects across the touched docs: no other bare fence,
no other placeholder inside a shell command, no other false absolute about how
the mixup surfaces. AppHost.Tests still 10/10; no executable code in this commit.
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.

F5: Dashboard — landing screen with today's numbers

1 participant