Conversation
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>
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
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Tests