145 Commits

Author SHA1 Message Date
735df89e13 fix: sort approved agenda items by time; add day headers to schedule view
All checks were successful
Client Check Build (NixCN CMS) TeamCity build finished
- Admin agenda page: approved items now sorted by start_time (nulls/zero-dates last)
- AgendaSchedule: group items by day with M月D日 header above each group

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-07 17:16:24 +08:00
5e41c73cfd fixup! docs: add agenda estimated-time field design spec
All checks were successful
Client Check Build (NixCN CMS) TeamCity build finished
2026-05-07 16:58:59 +08:00
b8f780b7b3 fix: add required and placeholder to estimated time input
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-07 16:50:45 +08:00
ccd65d0099 feat: add estimated time input to agenda submit dialog
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-07 16:47:07 +08:00
e7977db0cc feat: use agendaSubmitSchema in submitAgenda action; append estimated time to description
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-07 16:45:26 +08:00
a6f61e6e0d feat: add agendaSubmitSchema with estimatedTime field
Extends agendaItemSchema with a required coerced integer estimatedTime
(1–999 minutes) for the agenda submit flow.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-07 16:39:46 +08:00
3edb321b3a docs: add implementation plan for agenda estimated-time field
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-07 16:16:11 +08:00
7bd27153ab docs: add agenda estimated-time field design spec
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-07 16:12:19 +08:00
96453ae197 feat: add pagination and KYC detail modal to attendance page
All checks were successful
Client Prod Build (NixCN CMS) TeamCity build finished
Client Check Build (NixCN CMS) TeamCity build finished
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-04 01:53:31 +08:00
46994243e4 fix: use dayjs.utc for agenda item time display in admin agenda page
All checks were successful
Client Prod Build (NixCN CMS) TeamCity build finished
Client Check Build (NixCN CMS) TeamCity build finished
Replaced raw ISO string slicing with dayjs.utc() formatting, consistent
with the rest of the codebase, so agenda item times are not shifted by
the browser's local timezone offset.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-04 00:26:12 +08:00
635d5278e3 fix: nickname and subtitle length limit
All checks were successful
Client Prod Build (NixCN CMS) TeamCity build finished
Client Check Build (NixCN CMS) TeamCity build finished
2026-05-04 00:06:23 +08:00
c3291f5d5c fix: prevent double UTC+8 offset on event date display
All checks were successful
Client Prod Build (NixCN CMS) TeamCity build finished
Client Check Build (NixCN CMS) TeamCity build finished
The backend returns naive datetime strings already in UTC+8. dayjs was
re-applying the browser's local +8 offset, shifting date labels by one
day. Centralise dayjs configuration in src/lib/dayjs.ts with the UTC
plugin and switch all event start/end date formatting to dayjs.utc() so
the string is read as-is without timezone conversion.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-03 23:45:22 +08:00
cbe0ec88e4 fix: daisyui theme
All checks were successful
Client Check Build (NixCN CMS) TeamCity build finished
Client Prod Build (NixCN CMS) TeamCity build finished
2026-05-02 21:59:53 +08:00
46a2451e98 Remove antfu config from zed settings
All checks were successful
Client Check Build (NixCN CMS) TeamCity build finished
Signed-off-by: Asai Neko <sugar@sne.moe>
2026-05-02 20:23:25 +08:00
267f97a1e7 Change node version to 24 and pin pnpm version
Signed-off-by: Asai Neko <sugar@sne.moe>
2026-05-02 20:23:25 +08:00
a2d2f8c752 Init devenv shell
Signed-off-by: Asai Neko <sugar@sne.moe>
2026-05-02 20:23:25 +08:00
bd7ff728fb Make the primary color nix-themed 2026-05-02 20:23:25 +08:00
b5784d2e46 Add devenv for project
Signed-off-by: Asai Neko <sugar@sne.moe>
2026-05-02 20:23:25 +08:00
95539c3807 fix(profile): update UI constraints and tests to match new 5-255/24 limits
All checks were successful
Client Check Build (NixCN CMS) TeamCity build finished
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-19 00:58:29 +08:00
1a11af5ea4 fix: update username/nickname constraints to match backend
Username: 3–32 → 5–255. Nickname: max 64 → max 24 (UTF-8 rune limit).
Applied to both onboarding and profile schemas.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-19 00:55:07 +08:00
9d0e591412 docs(onboarding): reformat plan and spec to consistent tab indentation
All checks were successful
Client Check Build (NixCN CMS) TeamCity build finished
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-19 00:46:08 +08:00
f386ac8937 chore(onboarding): add cast comment and run lint
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-19 00:45:32 +08:00
c5784f80dc test(onboarding): add E2E tests for UUID-username onboarding dialog
Also fixes the `completeProfile` form action: SvelteKit only supports form
actions in `+page.server.ts`, not `+layout.server.ts`. Moved the action to a
dedicated `/(app)/onboarding/+page.server.ts` and updated the dialog form's
`action` attribute to the absolute path `/app/onboarding?/completeProfile`.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-19 00:44:06 +08:00
c4419a1193 feat(onboarding): add OnboardingDialog component and wire into app layout
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-19 00:29:43 +08:00
cb325692f7 feat(onboarding): detect UUID username and add completeProfile layout action
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-19 00:21:37 +08:00
5754c163d7 feat(onboarding): add onboarding Zod schema
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-19 00:14:57 +08:00
18de60285e docs: add onboarding dialog implementation plan
6-task plan: schema → layout server → component → layout wire-up → E2E tests → lint/build.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-19 00:10:56 +08:00
66ca0ed2a0 docs: add onboarding dialog design spec
Mandatory username/nickname gate for newly registered users (UUID username).
Layout-level detection + named action + Bits UI dialog, no dedicated route.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-19 00:02:21 +08:00
e80ad704d1 feat(sentry): add /vitals tunnel to bypass adblockers
All checks were successful
Client Check Build (NixCN CMS) TeamCity build finished
Routes Sentry envelopes through a first-party /app/vitals endpoint
instead of directly to *.ingest.sentry.io. DSN host and project ID
are validated server-side from the SENTRY_DSN env var before proxying.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-18 20:35:37 +08:00
e1009ed622 docs: add navigation progress bar design spec and implementation plan
All checks were successful
Client Check Build (NixCN CMS) TeamCity build finished
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-18 20:09:39 +08:00
e5acc171c6 feat(ux): add navigation progress bar for SSR load feedback
Thin primary-color bar at top of viewport appears after 200 ms of
navigation latency and snaps away when the new page renders. Uses
nprogress wired to SvelteKit's navigating store via a Svelte 5
$effect. Fast navigations (< 200 ms) produce no visible flash.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-18 20:09:18 +08:00
1cdefc4f49 fix(agenda): use badge-warning for pending status to fix contrast
All checks were successful
Client Check Build (NixCN CMS) TeamCity build finished
badge-neutral on the dark theme renders with insufficient contrast;
badge-warning (amber) is both readable and semantically correct for
an awaiting-review state.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-18 19:52:22 +08:00
60f7badbb7 fix(admin-events): redirect to list after event creation
All checks were successful
Client Check Build (NixCN CMS) TeamCity build finished
After creating an event, redirect to /admin/events instead of the new
event's edit page, which was confusing since the user hasn't configured
the event yet. Adds E2E test to assert the post-create redirect.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-18 19:50:39 +08:00
2c5426671e chore: replace hand-drawn NixOS SVG with official logo paths
All checks were successful
Client Check Build (NixCN CMS) TeamCity build finished
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-18 19:40:48 +08:00
be8b8c0d94 fix(sentry): use includes() instead of startsWith() for cdn-cgi filter
All checks were successful
Client Check Build (NixCN CMS) TeamCity build finished
window.onerror reports script URLs as absolute URLs, so f.filename is
"https://test.nix.org.cn/cdn-cgi/..." — startsWith('/cdn-cgi/') never
matched and Cloudflare email-decode noise kept reaching Sentry.

Fixes NIXCN-CMS-TEST-1

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-18 19:35:02 +08:00
972965e0ea fix(checkin): use SvelteMap for dedup cache, track plan + format docs
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-18 19:34:49 +08:00
d14fdf20d1 fix(checkin): strict 6-digit QR validation + 10s dedup cooldown
- Reject QR codes whose raw text is not exactly 6 digits (no strip-and-guess)
- Suppress repeated onScan calls for the same code within 10 s

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-18 19:30:15 +08:00
32555f21ab docs: add QR scanner dedup & strict validation design spec
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-18 19:25:37 +08:00
6059fa0214 chore(profile): remove 500-char limit from bio placeholder
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-18 19:20:40 +08:00
705af7f30b docs: add token refresh race condition design doc
All checks were successful
Client Check Build (NixCN CMS) TeamCity build finished
Documents the production 401-on-rotation race, the four-layer client-side
fix (proactive refresh, in-process single-flight, rotation cache, 401
interceptor), the backend AT lifetime extension to 5min, and every
alternative considered with the reasoning behind each rejection.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-18 19:12:21 +08:00
5de1518784 fix(session): raise AT lifetime to 5min, proactive buffer to 30s
Backend extended TTL_ACCESS to 300s. Matching the client-side constants
keeps proactive refresh in sync — fires 30s before expiry instead of
5s before a 15s token, reducing refresh frequency by ~20x.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-18 19:09:36 +08:00
ccb7680d38 fix(session): proactive token refresh to eliminate 401 rotation races
The backend does not implement a refresh-token grace window. Without it,
two browser requests sent within the same ~15s window but in separate
HTTP round-trips both carry the same expired access token. The first
request successfully rotates the token; the backend immediately invalidates
the old token; the second request arrives after the first response's
Set-Cookie has been sent but before the browser has applied it, so it
still presents the old token — and the backend rejects the refresh.

The recentlyRotated cache (previous commit) covers same-process sequential
races. This commit adds the primary defence:

Proactive refresh in hooks.server.ts: decode the access token's iat claim
(payload only, no signature verification) and rotate when ≤5s remain on
the 15s lifetime, before any API call is made. All parallel load functions
in the same request then see a freshly-issued token. The single-flight in
refreshSingleFlight collapses simultaneous proactive refreshes from
concurrent browser requests on the same pod. The 401 interceptor in
api.ts remains as a safety net for unexpected cases (clock skew, etc.).

Adds getJwtIat and isTokenAboutToExpire helpers with full unit tests.
Updates CLAUDE.md to document the two-path refresh design.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-18 18:12:12 +08:00
86ba252dde fix(session): cache rotation result to survive sequential refresh races
Root cause (confirmed from backend logs): when two browser requests are
in-flight simultaneously with the same expired access token, the second
request arrives at the backend ~1s after the first refresh completed.
By then the backend's grace window has closed and it rejects the already-
rotated refresh token with 401.

The existing single-flight mechanism only collapses truly concurrent
refreshes (requests that arrive before the first promise resolves). It
cannot help a sequential caller that arrives after the first refresh
completes and its inFlight entry is deleted.

Fix: keep a process-level recentlyRotated cache keyed by the old refresh
token for 10 seconds after a successful rotation. A later caller with the
same old token gets the cached pair immediately, without a second backend
call that would be rejected.

Adds a regression test that reproduces the exact scenario.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-18 18:07:35 +08:00
4f583e457d chore: use NixOS blue instead of currentColor in favicon SVG
All checks were successful
Client Check Build (NixCN CMS) TeamCity build finished
`currentColor` inherits black from the browser's favicon rendering context.
Hardcode #7ebae4 (NixOS light blue) so the icon is recognisable in the tab bar.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-18 17:55:44 +08:00
88e21a0bdc fix(session): log refresh failure reason for diagnostics
All checks were successful
Client Check Build (NixCN CMS) TeamCity build finished
When refreshSingleFlight fails, we previously returned null with no
context. Now each branch emits a console.warn with the relevant detail
(HTTP status, missing token pair, or thrown error message). Sentry's
consoleLoggingIntegration picks these up automatically, so the next
refresh failure will show the backend's actual rejection status in Sentry.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-18 17:47:22 +08:00
fe19146d3d fix(sentry): filter Cloudflare noise and redirect on expired session
All checks were successful
Client Check Build (NixCN CMS) TeamCity build finished
Fixes NIXCN-CMS-TEST-1: add beforeSend filter that drops events where
every stack frame originates from a Cloudflare /cdn-cgi/ script —
pure third-party noise from the email-decode injector.

Fixes NIXCN-CMS-TEST-2: when an API call inside a load function gets a
401 (session expired, refresh failed), loadSdk now throws redirect(303)
to /app/authorize instead of error(401). This gives the user a working
login redirect rather than an error page, and prevents SvelteKit from
passing the deserialized HttpError object to handleError where Sentry
was incorrectly capturing it as an unexpected exception.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-18 17:40:41 +08:00
a11af6e8b7 chore: use nixos.svg from static/ as favicon
All checks were successful
Client Check Build (NixCN CMS) TeamCity build finished
Replaces the Vite-processed $lib/assets/favicon.svg import with a direct
reference to static/nixos.svg via the base path, adding SVG MIME type.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-18 17:30:30 +08:00
a01c75a2ca chore: rename app to NixCN CMS and add browser title
Corrects misspelling "Nix CN CMS" → "NixCN CMS" across all source files,
docs, and tests; adds <title>NixCN CMS</title> to the root layout; documents
the correct spelling in CLAUDE.md.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-18 17:29:37 +08:00
ba6edca4fd fix(auth): disable login button until Turnstile verification completes
All checks were successful
Client Check Build (NixCN CMS) TeamCity build finished
Track verified state via on:turnstile-callback; button shows 等待 Turnstile...
spinner on load and remains disabled until CF widget succeeds or errors.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-18 17:27:11 +08:00
a143e4b563 fix(profile): remove 500-char limit on bio field
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-18 17:22:11 +08:00