- Walked: 2026-05-29 at
3678298cd3. The retained result cited an ephemeral/tmp/claude-oshun-lt-2-walkthrough/walk.mjs, confirmed that/nyx/eventsand/eventsrendered, saw a shared event list and ICS affordance, and reported no page errors or persistent request failures. It retained no downloaded bytes, event identity, authenticated member, preference write, provider connection, scheduled reminder, delivered notification, tap destination, observation, trace, or external receipt. - Reconciled: 2026-07-21 against both event authorities, both ICS generators, the browser and BFF action stores, the durable Nyx member store, provider OAuth and export routes, the opt-in reminder worker, delivery and Web Push code, the shipped service worker, both observation paths, focused unit suites, and all eight named Playwright files. The re-run below is local repository evidence, not a production provider, real device, restart, scheduler-clock, or external-calendar exercise.
- Verdict: partial / deep for the separated shipped seams — the dynamic agenda has a real whole-feed ICS export; the fixed rich catalog has a real browser-generated single-event ICS file; event-action preferences synchronize to a process-local mirror; a separate durable member-reminder record can feed provider export and an optional in-app worker; the service worker can route a supplied deep-link payload; and observations have an owner-scoped BFF record. No shipped path joins one rich-detail action to the durable reminder, provider write, real push payload, event navigation, and linked observation.
- Current authority:
WALKTHROUGH/journeys/nyx-event-calendar-sync-reminder.mdnow names the distinct event ids and state owners instead of presenting them as one reminder lifecycle.
Result at a glance#
| Interaction | Proof · limit |
|---|---|
| Historical route walk | Proof: both agenda routes rendered a shared list and ICS affordance. Limit: it neither downloaded the feed nor crossed an authenticated or external boundary. |
| Dynamic agenda | Proof: /nyx/events filters the real /v1/events/upcoming response to Nyx and /events shows the shared feed. The BFF builds lunar phases, season markers, and source-backed featured eclipse/meteor entries rather than fabricating a fallback. Limit: these generated ids are not the fixed rich-catalog ids. |
| Agenda ICS | Proof: /v1/nyx/events.ics returns a 365-day, at-most-36-event calendar with @oshun.app UIDs, timestamps, summaries, descriptions, categories, and folded lines. Limit: every row's Add to calendar and the page subscription control target this same full feed; a row click is not a single-event export. |
| Rich event catalog | Proof: /domains/nyx/events, /events/[eventId], and /domains/nyx/events/[eventId] render substantial fixed 2026 records. Limit: the catalog is a separate authored authority; fixed ids such as march-equinox do not match dynamic ids such as season-march-equinox-YYYY-MM-DD. |
| Rich event ICS | Proof: Add to calendar generates one browser download with a deterministic filename and <eventId>@oshun-nyx UID. Limit: there is no provider import acknowledgment, and this generator is unrelated to the agenda feed's bytes and ids. |
| Event-action controls | Proof: Save, Follow, Remind, cadence, and channels persist immediately in oshun:nyx:event-actions:v1; authenticated clients also PUT to owner-scoped BFF routes and hydrate the newer timestamp. Limit: the BFF store is only process memory, route labels are reconstructed metadata, and neither side calls a scheduler or provider. |
| Durable member reminder | Proof: the Nyx adapter validates a dynamic upcoming id and writes owner-scoped reminder state through the snapshot-backed member store; that record participates in export and erasure. Limit: the rich detail never calls this adapter, and its fixed ids generally fail the dynamic lookup. |
| Provider calendar | Proof: Profile exposes Google, Apple, and Outlook connection controls; Google/Outlook can OAuth when configured; credentials are snapshot-backed; calendar preview/export reads domain schedule items and reports per-provider outcomes. Limit: the Profile browser spec mocks its BFF boundary, Apple is fail-closed by default, the default transport only writes Google, and the Nyx schedule reads durable member reminders—not detail action state. |
| Reminder worker | Proof: when explicitly enabled, the BFF sweeps known users, converts enabled durable member reminders into stable scheduled reminders, and can deliver an in-app inbox item. Limit: the worker is disabled by default; the bridge supplies no external recipient and emits a nonexistent /nyx/events/<id> URL. |
| Service-worker tap | Proof: the actual sw.js preserves a manually supplied actionPath; a cold tap opens it, while an existing-window tap posts a message and focuses the tab. Limit: production delivery does not pass action data into Web Push, and PwaBootstrap does not handle OSHUN_NOTIFICATION_TAP, so this is not producer-to-event-navigation proof. |
| Observation handoff | Proof: rich detail links to the local-first observation log and the BFF stores owner-scoped observations durably; agenda rows use a separate direct-post form. Limit: the log shows eventId as a tag, not a return link; queued local failures have no replay path; and browser Nyx state is not subject partitioned. |
| Named automation | Proof: eight files contain 35 focused browser/API cases over the page, ICS, action-state, Profile/admin connectors, observation, and service-worker contracts. Limit: they are distributed contracts with mocked or injected boundaries, not one event-action-to-observation receipt. |
Evidence map#
Solid arrows are real handoffs. Dashed arrows are missing bridges or identity breaks. The diagram deliberately keeps the rich-detail preference, durable member reminder, provider export, and injected notification on separate lanes.
flowchart TB
J[Nyx calendar and reminder] --> A[Dynamic agenda]
J --> R[Fixed rich catalog]
A --> F[Whole-feed ICS<br/>dynamic ids · @oshun.app]
R --> I[Single-event ICS<br/>fixed ids · @oshun-nyx]
R --> P[Browser action preference<br/>process-local BFF mirror]
D[Assistant / Nyx adapter] --> M[Durable member reminder<br/>dynamic event id]
M --> C[Profile calendar export]
M --> W[Opt-in reminder worker<br/>in-app schedule]
W -. no actionPath payload .-> S[Service worker<br/>injected tap contract]
P -. no durable-reminder bridge .-> M
R --> O[Local-first observation log<br/>owner-scoped BFF row]
S -. existing tab only focuses .-> O
Proven observations#
The old pass proved repaired agenda routes, not calendar synchronization#
/nyx/eventsand/eventsdo shareEventList. The former filters the server result todomain === 'nyx'; the latter renders the cross-domain list.getUpcomingEventsrequests/v1/events/upcomingand returns an empty state on failure rather than manufacturing browser fixtures.- The BFF response is generated from the astronomy library: principal lunar phases and season markers are solved for the current window, while fixed eclipse and meteor templates are included only when their dates are in it. The event ids therefore encode occurrence and date.
- The historical walker saw a calendar label but did not establish what it
exported. Source shows that every event row appends the identical
/v1/nyx/events.icslink. The top-level subscription control uses that same href. A row-level label does not select the row.
There are two real ICS generators with different records and identities#
- The agenda endpoint is unauthenticated and emits the complete generated
astronomical window. Every VEVENT has a
DTSTAMP, UTC start, optional end, escaped descriptive fields, category, and<dynamic-id>@oshun.appUID. The response is private-cacheable for five minutes. - The rich catalog is bundled into
NYX_WEB_EVENT_RECORDSand has fixed 2026 records such asjupiter-venus-conjunction,march-equinox,perseid-meteor-shower-peak, andtotal-solar-eclipse-2026. Its detail workspace calls a browser-only helper that writes one VEVENT, uses<fixed-id>@oshun-nyx, and downloads<fixed-id>.ics. - Equivalent astronomical labels do not imply equivalent primary keys. The
dynamic authority uses ids such as
season-march-equinox-YYYY-MM-DDandperseid-meteor-shower-peak-2026. No reconciliation table maps those to the fixed detail ids. A past fixed event can still accept a UI preference because the action store validates shape, not current astronomical availability.
Detail actions persist preference metadata, not scheduled work#
- The detail hook owns one browser-global localStorage record. Mutations write
synchronously, notify the same tab through a custom event, and rely on the
storage event across tabs. With an access token, the hook also fire-and-
forgets an owner-scoped PUT and later merges local and remote values by
updatedAt. - The BFF route correctly requires a Nyx-domain bearer, validates event ids,
reminder times, cadence and channels, and caps the owner bucket. Its store is
composed of
Mapobjects and has no snapshot sink, startup binding, provider call, due-time index, or dispatcher. Restart loses this mirror. mobile-push, email, SMS, andcalendar-syncrows are reconstructed from the chosen channel strings. They make the UI state legible; no consumer resolves them into verified recipients or performs delivery.- Normal auth cleanup does not remove
oshun:nyx:event-actions:v1. On a shared browser, member A's values can remain visible to member B and, after a later mutation, be mirrored into B's server bucket. The owner-scoped BFF route does not repair a browser key that lacks a subject partition.
The real durable reminder belongs to another authority#
in-process-adapter.tsimplementssetEventReminderby rebuilding upcoming events, finding the supplied dynamic id, and then callingnyxMemberStateStore.addReminder. The stored row owns title, type, due time, cadence, channels, enabled state, and member id.- The server requires that member store's durability and wires it to the supported snapshot database. Its records participate in customer export and erasure. This is materially stronger than the event-action mirror.
- The assistant's
nyx.set_reminderaction can reach the adapter. No rich event-detail component calls it, and a fixed catalog id generally is not in the adapter's dynamic event set. Similar field names do not form a bridge.
Provider export is real, but it reads the durable reminder lane#
- Profile's Calendar section lists Google, Apple, and Outlook, previews the
domain schedule, starts OAuth, lists/removes connections, and calls the
separate calendar export route. Google and Outlook OAuth are available with
configuration. Apple deliberately returns
503 apple_calendar_oauth_unconfiguredin the default runtime. - Established OAuth connections, including refresh credentials, are stored behind a snapshot sink and exposed through secret-free views. Pending OAuth state is ephemeral. The default transport factory implements Google writes; other established providers need an injected/configured transport and otherwise report provider-unavailable outcomes.
buildUserDomainCalendarItemsincludes only enabled durable Nyx reminders whose channels are absent or includeexternal-calendar, and only inside its 14-day window. It never readsevent-action-state-store.ts.- Both the calendar item and reminder bridge emit
https://oshun.app/nyx/events/<dynamic-id>. The web app has no/nyx/events/[eventId]route; the rich routes are/events/[eventId]and/domains/nyx/events/[eventId], and those expect fixed ids anyway. The exported URL is therefore not a working event detail. profile-calendar-sync.spec.tsis useful UI/wire-shape coverage but fulfills all calendar BFF routes itself. The real two-way Fastify unit flow uses an injected provider transport.calendar-connectors-bff.spec.tsexercises the separate admin connector runtime, not member Profile OAuth.
The worker and notification test do not close the delivery loop#
produceNyxEventRemindersreads enabled durable member reminders and gives each one a stablenyx-event:<user>:<reminder>session id. The generic planner schedules aroundreminderTime. The server starts the autonomous worker only whenOSHUN_REMINDER_WORKER_INTERVAL_MSparses to a positive value; it is intentionally off by default.- The bridge labels the channel
pushbut supplies an empty recipient. Its reliable outcome is the owner-keyed in-app inbox path; an external push lacks a target. The delivery cycle can still report the person reached when in-app succeeds, so that aggregate result must not be read as provider acknowledgment. - The two service-worker cases load the shipped script in a VM and manually
inject a payload containing a valid rich route. That proves
actionPathsurvivesshowNotification, cold taps callopenWindow, and warm taps post a message before focusing a window. - The general Web Push transport supports optional data, but the delivery
dispatcher calls it with title and body only.
PwaBootstraphandles sync and update messages, notOSHUN_NOTIFICATION_TAP. Thus the tested payload is not produced by the durable reminder lane, and a tap with an existing app window does not navigate that window to the event.
Observation storage is real but remains a separate finish#
- Rich detail sends Log observation to
/domains/nyx/observation-log?eventId=<fixed-id>. The log immediately creates a local Zustand row withsyncState: queued, then attempts the authenticated BFF POST. Success marks it synchronized; failure leaves it queued, with no replay worker found in the audited path. - The BFF observation endpoint is owner scoped and durable through Postgres or the supported snapshot fallback. Its named API cases cover authorization, validation, defaults, and owner isolation. The UI renders Linked event as a tag, not an anchor back to the source detail.
- Agenda event rows instead link to
/nyx/observation?eventId=<dynamic-id>, a separate direct-post form. Neither path is the destination of a real reminder-produced notification. - The persisted
oshun.nyxZustand key is browser global and is not cleared by normal auth cleanup. Observations, equipment, and sky preferences can remain visible across accounts on a shared browser even though server rows are correctly owner scoped.
Boundaries and gaps#
- An agenda row is not the rich event record. Dynamic ephemeris ids and fixed detail ids have no reconciliation layer.
- A row-level calendar label is not a per-event export. Every agenda row downloads the same whole feed.
- A delivery-route label is not a dispatched channel. The detail state reconstructs labels without recipient resolution or transport calls.
- A process-local mirror is not the durable reminder authority. The action BFF map and snapshot-backed member reminder are different stores.
- A shared label is not a shared event identity. Equinox and Perseid names do not make their fixed and dynamic ids interchangeable.
- A durable member reminder is not created by the detail controls. The only audited creation path is the separate Nyx adapter/assistant action.
- A provider export is not event-detail calendar sync. Profile exports the durable schedule; detail downloads a file and never calls that route.
- An injected push payload is not producer-to-service-worker proof. The VM test supplies fields that the delivery call does not send.
- Focusing an existing tab is not navigating to the event. The posted tap message has no application handler.
- A linked-event tag is not a return link. Observation read-back preserves the id but does not make it navigable.
- Browser-local state is not subject-partitioned state. Both Nyx storage keys outlive normal account cleanup without a user namespace.
- Thirty-five cases are not one reminder-to-observation receipt. The named suites prove independent page, API, export, connector, and worker-adjacent contracts with mocks and injected payloads at important boundaries.
Re-run evidence#
python3 -m pytest tools/docs_center/tests/test_generator.py -qfinished with 53 passed. The Result 44 assertion pins registry position 44, front matter, section order, one evidence map, the 15 + 2 + 4 + 1 + 4 + 3 + 4 + 2 named case counts, both ICS authorities, both reminder stores, provider source, worker opt-in, notification-tap gap, broken canonical URLs, and shared-browser state boundaries.- Seventeen focused unit/integration files finished with 147 passed over astronomical events and feed ICS, fixed page models, action state/store/routes, durable member state, provider connection/export, the real two-way Fastify calendar flow, reminder bridge, observation UI, Web Push payload delivery, and PWA message handling. The three selected domain-stub route cases passed; its other 91 cases were intentionally filtered from this focused run.
- The eight named Playwright files ran serially with one Chromium worker. The
reused BFF passed 33 of 35 before the two seeded-admin assertions
correctly exposed its missing
OSHUN_CALENDAR_CONNECTORSenvironment; the whole three-case connector file then passed against an isolated correctly seeded BFF. Every named case therefore has a passing run, while remaining distributed evidence: Profile is mocked, the service-worker payload is injected, and no case performs one detail action → durable reminder → provider/worker receipt → tap → observation chain. - Docs generation produced 3,116 files / 2,787 reader pages; freshness accepted all 3,116. Integrity reported 0 structural broken links / 0 dead source paths across 3,485 entities, with 112 pre-existing imported-content warnings surfaced as nonblocking.
- The focused Docs Center reader case passed at desktop and Pixel 7 dimensions (2 passed). It checks partial status, substantive prose, all twelve boundary statements, Mermaid containment and pairwise node geometry, expand- control clearance, heading anchors, journey navigation, console/page errors, and horizontal overflow. Full-page screenshots at 1,440 px and Pixel 7 dimensions were also reviewed without a clipping or hierarchy defect.
Source trail#
- Dynamic agenda and feed:
apps/oshun/web/src/app/{nyx/events,events}/page.tsx,apps/oshun/web/src/components/nyx/EventList.tsx,apps/oshun/web/src/lib/server/nyx-depth.ts,apps/oshun/bff/src/routes/domain-stubs.ts, andlibs/oshun/domain-nyx/src/upcoming-events.ts. - Fixed catalog and per-event ICS:
apps/oshun/web/src/components/domains/nyx/nyxEventPageModels.tsandapps/oshun/web/src/components/domains/nyx/NyxEventDetailWorkspace.tsx. - Detail action state:
apps/oshun/web/src/components/domains/nyx/useNyxEventActionState.ts,apps/oshun/bff/src/nyx/event-action-state-store.ts, andapps/oshun/bff/src/routes/nyx.ts. - Durable reminder authority:
apps/oshun/bff/src/nyx/in-process-adapter.ts,apps/oshun/bff/src/nyx/nyx-member-stores.ts, andapps/oshun/bff/src/server.ts. - Provider calendar:
apps/oshun/web/src/components/profile/CalendarSyncSection.tsx,apps/oshun/bff/src/routes/calendar-connect.ts,apps/oshun/bff/src/routes/calendar-events.ts,apps/oshun/bff/src/calendar/calendar-oauth-connection-store.ts, andapps/oshun/bff/src/calendar/domain-calendar-events.ts. - Delivery and notification:
apps/oshun/bff/src/reminders/nyx-reminder-bridge.ts,libs/oshun/messaging-channels/src/delivery.ts,libs/oshun/messaging-channels/src/web-push-transport.ts,apps/oshun/web/public/sw.js, andapps/oshun/web/src/components/PwaBootstrap.tsx. - Observation and browser cleanup:
apps/oshun/web/src/components/domains/nyx/NyxObservationLog.tsx,apps/oshun/web/src/lib/stores/domain-stores.ts, andapps/oshun/web/src/lib/auth-context.tsx.
Cross-references#
nyx-tonight-observation.mdcovers the dynamic sky/observation surface and its own identity boundaries.messages-quiet-hours-and-channel-binding.mdseparates verified recipient binding from in-app and provider delivery.multi-device-workspace-handoff.mdcovers browser-local state and device-continuity limits.account-deletion-and-dsar.mdcovers the difference between durable export/erasure participation and unmanaged local browser keys.
Open questions#
- Which event registry should be canonical, and how should fixed rich records map to computed astronomical occurrences without silently changing identity?
- Should Remind create the durable member reminder through an authenticated mutation, and should action preferences then be removed or become a view of that record?
- Should agenda rows offer one-event ICS bytes, reserve the current link for a clearly labelled subscription, or provide both?
- Which provider transports are supported in production, and what per-event provider receipt should the UI expose after an explicit export?
- Should the reminder bridge resolve a verified push recipient, emit a valid
rich-detail URL and
actionPath, and route warm notification taps in the app? - Should Nyx localStorage/Zustand records be user partitioned and cleared on logout, with queued observation replay and conflict handling?
- What single trace id and same-event assertions should gate a future detail → reminder → delivery → event open → observation journey?