Skip to content

Design#2

Open
aaroncurtisyoga wants to merge 67 commits into
mainfrom
design
Open

Design#2
aaroncurtisyoga wants to merge 67 commits into
mainfrom
design

Conversation

@aaroncurtisyoga

@aaroncurtisyoga aaroncurtisyoga commented May 11, 2026

Copy link
Copy Markdown
Collaborator
  • Adds the new Next.js viewer (client/)
  • Removes the old Streamlit viewer (deletes app/ and its docker-compose service)
  • Updated the UI to match the version of Sepex currently deployed on papi (build from before Oct 28, 2024)
  • Removed UI pieces that rely on endpoints/fields the older Sepex doesn't have yet (Compute Resources panel, tags column on jobs, Inputs tab on job detail)

- replace globals.css with figma-make-output theme.css (dewberry brand
  vars, status enum, sidebar tokens, light/dark, hex chart colors)
- align env vars with the open-source config model: NEXT_PUBLIC_API_URL
  (with NEXT_PUBLIC_SEPEX_BASE_URL fallback), API_DOCS_URL,
  DEV_BYPASS_AUTH, keycloak/nextauth placeholders
- install next-auth@beta (wiring in bucket 2)
- update landing copy: open-source framing, drop the dropped text-cta
  utility for text-dewberry-teal
- gitignore design/ and untrack design/figma-make.md and
  design/scaffold-plan.md — internal iteration files don't ship publicly
- auth.js: NextAuth + Keycloak provider, mounted conditionally on
  KEYCLOAK_ISSUER so OSS clones / dev-bypass setups don't 500 on
  /api/auth/session with InvalidEndpoints
- auth-helpers.js: getAuthUser / requireAuthUser, no DB lookup —
  Sepex Viewer is stateless and identifies users to Sepex via the
  X-SEPEX-User-Email header
- devBypass.js: isDevBypass flag + devBypassUser stub gated on
  NODE_ENV=development && NEXT_PUBLIC_DEV_BYPASS_AUTH=true
- [...nextauth]/route.js: re-export handlers (no basePath shim —
  sepex-viewer mounts at /, unlike reality-view)
- providers.jsx: wrap with SessionProvider so client components can
  useSession()
- .env.example: rename NEXTAUTH_SECRET/URL → AUTH_SECRET/URL (v5
  naming) and document `npx auth secret` for generating one
Header right-side gains: cmd-k search button (lg+), light/dark theme
toggle via next-themes (rendered after mount to avoid hydration drift),
and a user popover backed by next-auth's useSession with sign-out and
dev-bypass label. Header itself is now sticky/backdrop-blur to match
the Make snapshot.
A small client component mounted in the dashboard layout listens for
cmd/ctrl+k and navigates to /jobs?focus=search. The Jobs page reads
that param, focuses its existing search input via a forwarded ref,
then strips the param from the URL so reloads are clean. The header
search button uses the same navigation, so click and shortcut behave
identically.
Mirrors the Make snapshot's md:hidden nav strip below the header so
small screens can still reach Dashboard/Builder/Jobs without the
desktop nav. Lives in its own MobileNav.jsx alongside Header to keep
Header focused.
Dashboard: the three jobs-driven charts (Jobs Over Time, Jobs by Process,
Top Submitters) gain isLoading / isError props. They were silently
showing 'No jobs in this window.' during the initial fetch, which read
as 'no data' instead of 'still loading'. Now they show a skeleton while
loading and a muted card while erroring.

Jobs: the table's empty state now distinguishes 'no jobs match these
filters' from 'no jobs have been submitted yet,' and the error state is
a top-of-table banner instead of replacing the entire table contents.

Job Detail: swap the loading state's hand-rolled animate-pulse divs for
the shared Skeleton component to match the convention everywhere else.
Header was the only file in the new phase 4 work using "./Sibling"
relative imports. Switch them to the @/app/_components/... alias to
match the convention used everywhere else in src.
Header.jsx and MobileNav.jsx both held identical nav route lists.
Move the array to its own module so adding/removing a route only
needs one edit.
mock layer at client/src/app/api/mock lets the viewer run end-to-end with
`npm run dev` alone. nine endpoints mirror the sepex ogc api - processes
contract, with an in-memory store on globalThis so it survives turbopack
hmr. newly-submitted jobs progress accepted -> running -> successful over
time so polling code paths get exercised. mock_scenario env var picks
between default / empty / error / lots-running / all-failed seeds.

ui fidelity to the figma make designs:
- run summary card shows elapsed alongside updated; new elapsed util
  reads created/updated and computes the duration string
- jobsovertimechart switches to horizontal stacked bars (was vertical
  recharts barchart); jobsbyprocesschart switches to horizontal bar
  list (was donut)
- new jobbreadcrumb replaces the back-link on the job detail page

dev quality:
- sheetdescription / dialogdescription added so radix stops warning,
  which clears the next dev-tools indicator on landing
- landing themeswitcher defers theme-dependent rendering until mount to
  fix an aria-pressed hydration mismatch
- playwright dev dep + capture-screenshots.mjs for repeatable visual
  diffs against figma make
color tokens
- light --muted-foreground darkens #78716c -> #57534e. was 4.40:1 on
  muted bg (fail), now 6.99:1. lifts every muted caption, tag chip,
  secondary label across the app.
- dark --dewberry-teal lightens #336b87 -> #5fa3c0. was 2.48:1 on the
  dark card (fail) for "open full page" link / icons / accents, now
  5.16:1. light-mode value preserved.

status pills
- bright pills (successful / running / accepted / failed) switch from
  white text to black. white was 1.92-3.76:1 on these greens / yellows /
  reds — universal wcag fail. black hits 5.58-10.95:1.
- outlined pills (dismissed / lost) now use slate-700/dark:slate-300 and
  purple-700/dark:purple-300 instead of the bare status color (pure
  status-lost was 3.96:1 on white).

local fixes
- logstab inactive level filter drops the text-muted-foreground/60
  opacity tint (~2.9:1 fail) and rides the global muted bump.
- failedjobsalert "view all failures" link uses red-700/dark:red-400
  instead of text-status-failed (small 12px text needs full 4.5:1,
  status-failed only hit 3.76:1 on the alert tint).

capture script
- adds 390x844 mobile viewport. refactored to a pages x themes x
  devices matrix so the script outputs 21 shots covering both viewports
  in both themes from one run.
- /jobs/{id}/metadata now returns the OGC shape (@context, apiJobId,
  process, image, commands, timestamps) that real Sepex emits; drops the
  fabricated runtimeSeconds/workerHost/pluginVersion fields.
- /jobs/{id} GET no longer includes a `message` field. Real Sepex only
  returns `message` on DELETE responses, not on JobRecord GETs.
- Replaces the hand-written 50-job seed with a deterministic generator
  (~5k jobs, seeded LCG, statuses weighted by recency) so Dashboard
  charts and Jobs pagination look like production scale.
- Adds a `lastErrorMessage` field on failed seed jobs for downstream
  consumers; exposing it via the API is feature-flagged separately.
Adds a single env-var feature flag (default off) so the Viewer reflects
what the current Sepex API can support today, but can flip on once items
A/C/D/H/I from research/proposed-next-steps.md ship.

Off (default — matches real Sepex):
- Dashboard hides the time-range picker; subtitle notes the 100-job cap
- Dashboard fetches cap at limit=100 (real Sepex silently clamps there)
- FailedJobsAlert renders just job metadata; falls back to a "fetch logs
  to see the reason" note
- ⌘K navigates to /jobs?focus=search (the v1 from pm-meeting.md §B)
- Jobs pagination footer shows "Page N" only — no "of M"
- Mock /jobs ignores ?q=, ?updatedAfter, ?updatedBefore; omits total/pages
- Mock /jobs response strips lastErrorMessage even on failed jobs

On:
- Time-range picker visible, dashboard fetches 200/500/1000 by range
- FailedJobsAlert shows inline lastErrorMessage per failed-job row
- ⌘K opens a real cmdk command palette with server-side search across
  jobID/submitter/processID/tags + quick actions
- Jobs pagination shows "Page 3 of 250"
- Mock /jobs honors ?q=, ?updatedAfter, ?updatedBefore and returns
  total/pages

Also adds two scripts under client/scripts/ for capturing proposed-state
screenshots and eyeballing the degraded form, and documents the env var
in docker-compose.yml.
Figma Make rendered status chips with white text on bright 500-level
backgrounds. Commit fce7d9b reversed that to black text because white
hit 1.92–3.76:1 on those colors — universal WCAG AA fail. Black passed
contrast but the chips no longer matched the Figma reference, and the
landing page LifecycleCard still used white text, so chips looked one
way on / and another way on /jobs.

Darkening the status tokens to 700-level shades restores the Figma's
white-text aesthetic AND keeps every chip above 4.5:1:

- successful #22c55e → #15803d (5.8:1)
- running    #eab308 → #b45309 (5.1:1) [shifts from yellow to amber]
- accepted   #0ea5e9 → #0369a1 (5.8:1)
- failed     #ef4444 → #b91c1c (6.6:1)

dismissed and lost stay unchanged since they render as outline pills,
not solid backgrounds.
JobIdLink shows jobID.slice(-8), assuming the 8-char hex format real
Sepex emits (per the c5h0g1e4-style examples in proposed-next-steps.md).
The earlier `mock-NNNNN-<processID>` seed format meant slice(-8) just
showed the trailing process name, so every terrain-extract row read
"extract", every report-pdf row read "port-pdf", etc.

Switches the mock to deterministic 8-char hex IDs derived from the seed
index, so the JobIdLink display works correctly and the mock looks more
like production data. Logs/results/metadata generation now follows a
new _seedIndex field on each job instead of parsing the jobID string.

Also updates capture-screenshots.mjs to fetch a successful jobID from
the API instead of hardcoding one, so it stays valid across seed
regenerations.
Wired the client at the dewberryanalytics papi instance and removed
surfaces that either 404 or render perma-empty against the current
Sepex API. Each removal has a concrete API gap behind it; nothing
trimmed for aesthetics.

- compose: point client at papi.dewberryanalytics.com, comment out
  local sepex/minio/postgres services
- dashboard: remove compute resources tile (/admin/resources 404);
  un-gate time range picker so users aren't stuck on 24h;
  swap toLocaleTimeString for compact relative time on activity rows
- job detail: drop Inputs tab and Elapsed field (neither field is
  returned by /jobs/{id}); swallow 404 on /results and /metadata so
  failed jobs show empty states instead of red error banners
- logs: read container_logs (real api) alongside process_logs (mock);
  detect inline WARNING:/ERROR prefixes for level classification;
  rename "Tail" to "Auto-scroll" with icon and tooltip; show per-level
  counts on filter pills; skip Go zero-time timestamps
- results: handle the {outputs: {links, results}} envelope returned
  by real Sepex (was treating top-level keys as outputs)
- jobs list: remove Tags column, "Run (from tags)" column, and tags
  filter state — listing has no tags field
- builder: drop ExecutionModeCard (only async-execute is advertised
  in jobControlOptions) and RecentPayloadsPopover (inputs aren't
  echoed back so cloning was clearing the form); submit async-only
- command palette: stop sending ?q= since the api ignores it
- landing: trim copy claiming resource gauges and queue depth; hide
  "view api docs" when no docs url is configured (papi has no /api)
- shared: link top submitters bars to /jobs?submitter= for parity
  with the by-process drilldown
- delete client/.env.local + client/.env.example; all client-side
  vars now live in the root .env / .env.example (NEXT_PUBLIC_*,
  KEYCLOAK_*, AUTH_*). Docker-compose already loads the root file
  via env_file on the client service
- drop NEXT_PUBLIC_DEV_BYPASS_AUTH — the dev-auth-bypass code path
  was removed in e9efc0c
- drop startup-dev.sh and its wait-for-sepex TCP probe; the script
  was theatre against papi:443 and the local sepex service is
  commented out. CMD now runs `npm run dev` directly
- docker-compose: remove SEPEX_HOST/SEPEX_PORT (no script to wait
  on) and the NEXT_PUBLIC_* environment block that duplicated .env
- client/.gitignore: prune the .env* block since nothing lives in
  client/ anymore
- prefers-reduced-motion media query in globals.css
- form labels associated via htmlFor/id (Submitter, SaveTemplate,
  DynamicInputField); aria-required, aria-invalid, aria-describedby
  on the controls and error/description nodes
- LogsTab: aria-pressed on Process/Server toggles, aria-expanded +
  aria-controls on group collapse buttons
- heading hierarchy: bumped section-level h3 → h2 across landing,
  dashboard, and builder pages so h1 → h2 → h3 holds
- charts: SparklineCard and JobsOverTimeChart wrapped in role="img"
  with descriptive aria-label; decorative bars marked aria-hidden;
  list-style charts get section + aria-labelledby
- ProcessPicker rebuilt on Popover + Command (cmdk) for full keyboard
  support, role="combobox", aria-expanded, aria-haspopup
Two papi processes had mismatches with the auto-generated form:

- fdt + pyecho use dataType: "value" — a Sepex catch-all for free-form
  literals. The form was treating those as object/list and showing a
  JSON textarea, so users had to type quoted strings. Now treated as
  text-like (same path as "string")
- dss_to_zarr.input_dss_keys (maxOccurs=100) and infer-ras-crs.counties
  (maxOccurs=9999) accept arrays, but the form sent a single value.
  Multi-occurrence inputs now render a one-per-line textarea; integer
  arrays are coerced to numbers at the submission boundary
… state

- skip-to-content link in root layout; id="main-content" on main landmarks
  (landing + dashboard layout)
- per-section page titles via metadata template ("%s · Sepex Viewer"):
  Dashboard, Jobs, Builder, and Job <id> via generateMetadata
- StatusIcon takes optional label prop; passed in RecentActivityFeed and
  RunSummaryCard so SR users hear the job status (icon was the only cue)
- TimeRangePicker dropped tab/tablist roles (no roving tabindex was wired);
  switched to button group with aria-pressed
- disabled buttons get cursor-not-allowed + grayscale on top of opacity-60
  so the affordance reads instead of just looking faded
Pushes Lighthouse accessibility from 90/94/96/100 to 100 across landing,
dashboard, jobs, and builder.

Color contrast: status / destructive colors are deliberately darkened so
white-on-pill chips clear AA (5.8:1+). When the same tokens were used as
TEXT on dark cards they only hit ~2.9–3.7:1. Introduced paired -fg
variants (text-status-successful-fg, text-destructive-fg, etc.) that equal
the base in light mode and lighten in dark mode. Pill backgrounds are
unchanged; only text usages swap to -fg. Updated 12 call sites: error
banners, FailedJobsAlert, log line ERROR/WARN, validation summary, form
messages, the +12% sparkline label, and the destructive ghost buttons.

Combobox names: 4 role="combobox" triggers had no accessible name (the
visible value isn't a name per the combobox AAM rules). Added aria-label
on ProcessPicker and the three Radix Select triggers in JobsFilterBar
and PaginationFooter ("Process", "Filter by process", "Filter by status",
"Items per page").
Smoke-tested the builder by submitting one pyecho job to papi
(job 28313064). The submission succeeded and round-tripped, but the
Results tab would have rendered three garbage rows (jobID, updated,
outputs) because normalizeResults didn't handle a scalar `outputs`
field.

The Sepex API returns sync echo-style outputs as a flat string:
  { "outputs": "sepex-viewer smoke test 2026-05-11T..." }

Now scalar `outputs` (string/number/boolean) becomes one normalized
entry with a `value` field; the tab renders the value inline in a
<pre> block. Object-map entries with scalar values get the same
treatment. The hms-runner-style { outputs: { links, results } }
envelope still wins when present.
# Conflicts:
#	.gitignore
#	README.md
@aaroncurtisyoga aaroncurtisyoga requested a review from slawler May 11, 2026 23:21
@aaroncurtisyoga

Copy link
Copy Markdown
Collaborator Author

@slawler Heads up — a lot of the new lines here are auto-generated. shadcn/ui writes component source directly into the repo when you run npx shadcn add <component> instead of shipping as an npm dependency, so the 18 files under client/src/components/ui/ (~1.5k lines) are basically machine-generated boilerplate. The npm lockfile (client/package-lock.json) is another ~10.5k. Combined, those two are about 63% of the 18,855-line diff.

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.

1 participant