Skip to content

Merge develop into main#45

Merged
ut42tech merged 38 commits into
mainfrom
develop
Jun 4, 2026
Merged

Merge develop into main#45
ut42tech merged 38 commits into
mainfrom
develop

Conversation

@ut42tech

@ut42tech ut42tech commented Jun 4, 2026

Copy link
Copy Markdown
Owner

Summary

  • Refresh the admin UI with responsive navigation, theme/PWA support, official branding, and restrained motion.
  • Add shared data loading/error/empty-state primitives and migrate admin flows to useApiResource.
  • Migrate check-in pages to the shared data layer and add full-screen kiosk error handling plus supporting docs.

Tests

  • Not run as part of PR creation.

ut42tech and others added 30 commits June 2, 2026 16:08
Responsive layout (sidebar + bottom nav), installable PWA, and
dark mode for the admin app. Brainstormed scope and decisions.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Introduces a shared next-themes wrapper (ThemeProvider) and a
light/dark/system ThemeToggle, and updates the Sonner Toaster to follow
the active theme. Falls back to light when no provider is present, so
checkin/signage are unaffected.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…iring

Replace the desktop-only top header/nav with an adaptive shell: a fixed
left sidebar on desktop and a mobile top bar + bottom tab bar (with
safe-area inset) on small screens, both driven by a single nav-items
source of truth. Wrap the app in ThemeProvider for dark mode.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add a web manifest (Android/Chromium), appleWebApp metadata (iOS), a
theme-aware viewport (light/dark status bar, zoom allowed), and
programmatically generated app icons (icon.tsx / apple-icon.tsx). No
binary assets committed; mirrors checkin's standalone, no-offline setup.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Wide data tables (dashboard, participants, mentors, pre-registrations)
now render as stacked cards under md and as tables at md+, via a shared
RecordCard/RecordField. Stats keeps its narrow table with a 2-up KPI
grid on mobile. PageHeader stacks, summary cards shrink, and the login
page gets a brand mark. Also fixes a nested <main> in the shell.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Chrome rejected the .ico manifest entry as an invalid image; keep only
the generated /icon PNG (favicon stays auto-linked for the tab). Hide
summary-card icons under the sm breakpoint so KPI labels have room.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
PreToolUse: block writing secret/lockfile (.dev.vars*, .env*, service-account JSON, pnpm-lock.yaml) per CLAUDE.md §7; PostToolUse: run Biome autofix on edited files to keep CI green. Also broaden .gitignore to .dev.vars.* (wrangler env-specific secret files).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Read-only reviewers encoding project constraints CI cannot check: Cloudflare Workers compatibility (CLAUDE.md §1/§2) and the children's-data PII boundary (CLAUDE.md §5).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
create-migration wraps the drizzle-kit generate -> wrangler D1 apply flow; pre-pr-check mirrors CI (Biome + type-check), runs @tecnova/shared tests CI omits, and scans the diff for secrets before commit.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- bottom-nav: active tab used dark --primary (2.6:1, an inversion); use
  the lighter sidebar-primary in dark to meet WCAG AA, add focus-visible ring
- stats: loaded KPI grid was missing the base grid-cols-2, so it stacked
  1-up on mobile and the total's col-span-2 was dead — restore 2-up
- mobile top bar: enlarge theme/account controls to 40px touch targets
- record-card: enrich tappable-card aria-labels with grade + status
- mentors: document the card/row dual-mount edit-state trade-off

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Replace the placeholder "tec" wordmark with the official logo
(public/logo_tecnova.png, the same asset checkin/signage use).

- BrandLogo component renders the logo; the wordmark is dark, so on
  dark-mode surfaces it sits on a white plate (dark:bg-white) to stay
  legible while preserving the brand colors (no plate in light mode).
- icon.tsx / apple-icon.tsx now embed the logo centered on a white
  square via ImageResponse (readFile -> data URL; Satori needs a native
  <img>, so next/image is intentionally not used).
- Drop the unused Next.js scaffolding SVGs from public/.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Mirror checkin's "Cohesive Elevation" motion (motion/react), all
prefers-reduced-motion aware:
- Shared tokens in lib/motion.ts; Reveal (one cohesive section/body
  fade-up) and AnimatedNumber (summary count-up).
- Sliding active indicator via shared layoutId: a filled pill in the
  desktop sidebar and a bar in the mobile bottom nav.
- Login adopts the logo (decorative alt to avoid double-announce next to
  the brand text) plus an entrance and CTA tap feedback.

Each page wraps its swapping data area in a single always-mounted Reveal
(flex flex-col gap-6 preserves the layout gap) so the entrance plays once
and does not replay on every refetch/search; no per-card stagger.

Nav a11y/contrast (from review): add aria-current + aria-label to the
sidebar nav, and use the AA-compliant primary pairing for the active pill
in light mode (light --sidebar-primary was 3.77:1), matching bottom-nav.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Note the BrandLogo dark-mode plate, the motion approach (Reveal /
AnimatedNumber / layoutId nav indicators, all reduced-motion aware), and
that the PWA icons embed public/logo_tecnova.png via ImageResponse.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The design system makes buttons pill-shaped (rounded-4xl), but the
sidebar active-pill background was rounded-md, so the active item read as
a rounded rectangle behind a pill-shaped button. Use rounded-4xl so the
indicator matches the button (and the ghost hover) shape.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The menu content already carried the data-open/data-closed enter/exit
classes (zoom/fade/slide) and tw-animate-css provides the keyframes, but
a stray `animate-none!` (!important) in the same className forced
`animation: none`, so the menus popped in/out with no transition.

- Drop the `animate-none!` override on DropdownMenuContent /
  DropdownMenuSubContent and SelectContent.
- Switch Select to position="popper" so the menu opens below the trigger
  (the component already had popper support wired); item-aligned
  positioning intentionally suppresses animation, which is why it never
  played. No new dependency needed (Animate UI not required).

In practice admin is the only consumer of both components.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ntent

The loading fallback was two generic blocks that looked nothing like the
final layout, so the swap to real data jumped. Replace it with a
content-shaped placeholder: a stat-card skeleton (badge + id + 2-col
label/value grid) and a history section (heading + rows), mirroring
DetailBody so the transition is smooth.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ub-project plan)

Records the Next.js 16.2.4 research conclusions (PPR/cacheComponents and
server-side streaming do not fit the current all-client-fetch +
host-only cross-origin cookie architecture) and decomposes the work into
three sequenced sub-projects: (1) client data-layer consolidation,
(2) resilience + instant shell, (3) server-side data (BFF vs
cross-subdomain cookie — its own design).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Shared read-only data primitives to consolidate admin's duplicated fetch
state machines:
- useApiResource<T>(path, {enabled}) — idle|loading|ok|error + reload(),
  refetches when `path` changes (query string drives search/filter/page),
  with the cancellation guard lifted from participant-detail-sheet.
  Mutations are out of scope (read-only).
- DataError — the destructive Alert used for fetch errors.
- EmptyState — the centered "no results" placeholder (non-table).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Replace the duplicated {loading|ok|error} useEffect+apiJson state
machines across the dashboard, participants, stats, mentors,
pre-registrations and the participant detail sheet with the shared
useApiResource hook, and the inline error Alerts / empty blocks with
DataError / EmptyState.

- Query-param-driven pages build the path (date / search+filter+page /
  date-range) and rely on refetch-on-path-change; the refresh button and
  post-mutation callbacks call reload().
- Mutation callbacks become () => void (reload is fire-and-forget); the
  POST/PATCH/DELETE calls still use apiJson/apiFetch directly.

No UI/behavior change. Verified: load, reload, search refetch, detail
sheet, and DataError/EmptyState states all render as before.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
MeProvider split into state-only MeProvider + MeGate (backward-compatible:
checkin/signage wrap content in MeGate, behavior unchanged). admin renders
the shell chrome outside the gate (skeleton nav/account until /api/me),
plus (authed)/error.tsx + root error.tsx + (authed)/loading.tsx.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Split the shared MeProvider into a state-only MeProvider (fetches /api/me,
handles 401, always renders children + provides state) and a MeGate that
does the gating (renders content only when ok, else loading/forbidden/error).
useMe() keeps its non-null signature (safe inside a gate); new useMeState()
feeds the shell. This is an atomic contract change, so all consumers move
together:

- checkin / signage: wrap content in <MeGate> (props moved over) — behavior
  unchanged (the gate logic is the same code, just relocated).
- admin: AppShell renders OUTSIDE the gate so the sidebar/top-bar chrome +
  brand logo paint immediately; sidebar/bottom-nav/mobile-top-bar use
  useMeState() and show skeleton nav/account until /api/me resolves, then the
  real role-filtered nav. Page content stays gated by <MeGate>.

Verified: admin shows the shell + skeleton nav during loading (desktop &
mobile), real nav/account on ok, MeGate forbidden on 403; checkin/signage
render through the relocated gate with no module/render errors.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- (authed)/error.tsx and root error.tsx ('use client') catch render-time
  throws that the per-page try/catch can't, reusing DataError + a reset button.
- (authed)/loading.tsx renders a content skeleton during soft navigation;
  meaningful now that the shell persists across navigations.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
feat(admin): responsive UI refresh — adaptive nav, PWA, dark mode, logo & motion
feat(admin): データ層モダン化(SP1)+即時インタラクティブシェル(SP2)を develop へ統合
Migrate checkin's 5 hand-rolled GET fetch state machines to the shared
useApiResource hook (from #43), and consolidate the 4 near-identical
full-screen error screens into a checkin-local CheckinErrorScreen.
Loading skeletons stay content-aware/page-local; signage stays as-is
(polling). Auth untouched.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The shared useApiResource gains a backward-compatible reload({ background })
(stale-while-revalidate); history's 更新 and admin's refresh/mutation
refetch opt in to avoid the skeleton flash.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ource

reload({ background: true }) keeps current data visible during refetch
instead of flipping to loading, for flicker-free refresh buttons.
Backward compatible: default is the existing loading behavior.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…rors

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
ut42tech and others added 8 commits June 3, 2026 00:12
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… refresh)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…refetch

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The prune useEffect depended on presentIdSet, which is recreated every
render while state != ok (sessions = data?.sessions ?? [] makes a fresh
[] each render), so the effect ran every render and setSelectedIds
(ids.filter always returns a new array) re-rendered forever — "Maximum
update depth exceeded" on the loading screen.

The effect was also redundant: selectedPresentIds already filters
selectedIds by presentIdSet at use-time, and every consumer (count,
checkbox state, checkout) reads the present-filtered value, so stale
stored selections are inert. Deleting the effect fixes the loop with no
behavioral change.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
refactor(checkin): propagate useApiResource + add background reload
@vercel

vercel Bot commented Jun 4, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
tecnova-platform-admin Ready Ready Preview, Comment Jun 4, 2026 3:00am
tecnova-platform-checkin Ready Ready Preview, Comment Jun 4, 2026 3:00am
tecnova-platform-signage Ready Ready Preview, Comment Jun 4, 2026 3:00am

@ut42tech
ut42tech merged commit 226a0e1 into main Jun 4, 2026
5 checks passed
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