nyx-event-detail.mdnyx-events.mdnyx-observation.mdprofile.mdJourney flow#
Generated from the authored steps below — click a node to jump to that section.
Nyx exposes useful calendar, preference, durable reminder, delivery, and
observation capabilities, but today they form several separated seams with two
event registries. The dynamic /nyx/events agenda exports a whole astronomical
feed. The fixed /domains/nyx/events catalog exports one browser-generated
event. Rich-detail action controls write browser state and a process-local BFF
mirror. A separate assistant/Nyx-adapter path owns durable member reminders;
only that state feeds provider calendars and the optional reminder worker.
Service-worker notification and observation coverage prove further independent
contracts. This journey must preserve those boundaries until one same-event
receipt joins them.
Personas#
- Agenda watcher — scans computed astronomical events and subscribes to the full calendar feed.
- Rich-event reader — opens an authored 2026 detail and downloads a single ICS file.
- Reminder planner — records a cadence and channel preference, or asks the assistant to create a durable reminder.
- Connected-calendar member — authorizes a provider in Profile and exports the member schedule.
- Observer — records an owner-scoped observation associated with an event id.
Pre-conditions#
- Use
/nyx/eventsfor the dynamic Nyx agenda and/eventsfor the shared cross-domain agenda. These call/v1/events/upcomingand use computed ids. - Use
/domains/nyx/eventsfor the fixed rich catalog. Its detail routes are/events/<fixed-id>and/domains/nyx/events/<fixed-id>; there is no/nyx/events/<id>detail route. - Do not treat matching labels as matching records.
march-equinoxandseason-march-equinox-YYYY-MM-DD, for example, belong to different authorities. - Use an authenticated session for action-state mirroring, durable member reminders, provider connections, exports, and BFF observation rows.
- Configure a supported provider before expecting OAuth or export. Profile displays Google, Apple, and Outlook; Apple is fail-closed by default, and the default writer transport supports Google.
- Enable
OSHUN_REMINDER_WORKER_INTERVAL_MSwith a positive value before expecting autonomous in-process reminder delivery; the worker is off by default.
Steps#
1. Choose the event authority before choosing an action#
For an agenda/subscription path, open /nyx/events. The page filters the real
upcoming-events response to Nyx records. Lunar phases and seasons are computed
for the current window, while featured eclipse and meteor records are
source-backed templates included only when upcoming. The shared /events page
uses the same response without the Nyx-only filter.
For a rich detail, open /domains/nyx/events and select a bundled record. These
fixed 2026 records carry description, timing, visibility, guidance, and
recommendations. They are not detail views over the dynamic agenda. Record the
actual id and source in evidence; a human-readable astronomical name is
insufficient.
2. Export the appropriate ICS contract#
On either agenda page, Subscribe (.ics) and every row-level Add to
calendar (.ics) link resolve to /v1/nyx/events.ics. The endpoint exports the
whole 365-day astronomical feed, capped at 36 records, with @oshun.app UIDs.
The row label does not narrow the bytes to that event.
On a rich detail, Add to calendar calls downloadNyxEventICalFile. It
downloads exactly one fixed record as <eventId>.ics, with an
<eventId>@oshun-nyx UID. The browser/OS then owns import. Neither file path
provides an external-client acknowledgment, and the two generators must not be
compared as if their ids or UIDs represented the same event record.
3. Treat rich-detail action controls as preference metadata#
Save, Follow, Remind, cadence, and channel choices write immediately to
oshun:nyx:event-actions:v1. Same-tab and cross-tab events refresh the UI. When
an access token exists, the hook also PUTs the selected state to
/v1/nyx/event-actions/<eventId> and hydrates the newest timestamp.
The BFF action store is an owner-scoped process-local map with no snapshot sink. It validates the request and reconstructs route labels, but it has no due-time worker, verified-recipient join, provider export call, or channel dispatcher. Selecting push, email, SMS, or external calendar is not evidence that any route was scheduled or delivered. The browser key also lacks a member partition and is not removed by normal auth cleanup.
4. Create a durable reminder through the separate Nyx member authority#
The assistant nyx.set_reminder action reaches the Nyx adapter's
setEventReminder. That adapter rebuilds the dynamic upcoming-event set,
requires an id in it, and writes title, type, reminder time, cadence, channels,
enabled state, and owner into the durable Nyx member store. The server requires
and wires this store's snapshot persistence; export and erasure include it.
No rich-detail component calls this mutation. Fixed catalog ids generally do not pass its dynamic lookup. There is no bridge that turns the saved Nyx event-action record into this durable member reminder. A durable reminder may therefore be real while the detail control still has no path to create it.
5. Connect and export a provider from Profile#
At /profile?path=calendar, inspect the schedule preview and manage Google,
Apple, or Outlook connections. Google and Outlook may complete OAuth when their
configuration is present. Apple currently returns the explicit unconfigured
503 boundary. Established OAuth credentials are durable and secret-free in
member views; pending OAuth state is only process memory.
/v1/calendar/export builds the member schedule and reports provider outcomes.
Its Nyx items come only from enabled durable member reminders whose channels
permit external-calendar, inside the 14-day horizon. The rich event-action
store is never read. The generated Nyx canonical URL currently uses the missing
/nyx/events/<dynamic-id> route, so even a successful provider write does not
prove a valid detail deep link.
6. Distinguish optional in-app delivery from push navigation#
When the worker is enabled, produceNyxEventReminders sweeps durable enabled
member reminders and creates stable scheduled entries. The bridge's recipient is
empty and its channel is labelled push; the dependable path is the owner- keyed
in-app inbox, while an external transport lacks a recipient. It also uses the
missing /nyx/events/<dynamic-id> URL.
The service-worker E2E independently injects a payload with a valid fixed rich
route. A cold notification tap opens it. With an existing app window, the
service worker posts OSHUN_NOTIFICATION_TAP and focuses the window, but the
app bootstrap does not consume that message. The normal Web Push dispatcher does
not pass the optional action data. This test is a notification contract, not
proof that the durable Nyx reminder produced a navigable push.
7. Record the observation through the matching surface#
Rich detail links to /domains/nyx/observation-log?eventId=<fixed-id>.
Submitting creates local oshun.nyx state first, then attempts an authenticated
BFF write; success marks the local row synchronized. The server record is owner
scoped and durable. The displayed Linked event value is a tag rather than a
return link, and a failed local-first row has no audited replay worker.
Agenda rows instead link to /nyx/observation?eventId=<dynamic-id>, a separate
direct-post form. Neither observation surface is reached by the current real
reminder producer. The global oshun.nyx browser key is not member partitioned
or cleared on logout, so local evidence and server evidence must be reported
separately.
Post-conditions#
- A feed download can be identified by its dynamic event ids and
@oshun.appUIDs; a rich-event download can be identified by one fixed id and@oshun-nyxUID. - A rich-event action preference can be read from the current browser and, for an authenticated member, from the current BFF process. It is not a scheduled reminder.
- A durable member reminder can survive through the supported snapshot store, export, and erasure, but must be reported as created by the separate adapter path.
- A provider export can report per-provider success or failure for eligible durable schedule items. It does not acknowledge importing a downloaded ICS file and is not invoked from rich detail.
- An enabled worker can create an in-app delivery record; a real push deep link requires recipient resolution and action data not proven here.
- An observation BFF row can be read back for its owner. A local queued row and a non-navigable event tag do not close the calendar/reminder journey.
Failure modes#
- Wrong event authority — a fixed detail id is supplied to the dynamic reminder adapter, or a dynamic agenda id is expected to resolve in rich detail.
- Mislabelled export expectation — the row-level agenda link downloads the entire feed rather than one VEVENT.
- Action-store restart — the BFF preference mirror disappears; the global browser record remains and can cross account boundaries.
- No durable bridge — changing a rich-detail cadence/channel never creates a member reminder.
- Provider unavailable — credentials or a writer transport are absent; Apple is unconfigured by default, and Outlook has no default export transport.
- Broken calendar/reminder link — provider and worker items use a route that does not exist and an id the rich catalog may not know.
- Worker disabled or recipient absent — no autonomous tick runs, or external push cannot resolve a target even though in-app delivery succeeds.
- Warm notification tap — the existing tab focuses without navigating because the application ignores the posted tap message.
- Observation sync failure — local queued data remains without server read-back or replay; local state can also survive account change.
These are the twelve separations the result must keep visible: agenda versus rich record; feed versus single-event ICS; action label versus dispatched channel; process mirror versus durable reminder; shared label versus shared id; durable reminder versus detail control; provider export versus detail sync; injected push versus produced push; focus versus navigation; tag versus return link; browser storage versus subject partition; distributed cases versus one receipt.
E2E coverage#
apps/oshun/web/e2e/nyx-event-pages.spec.tshas 15 route, catalog, detail-action, and observation-link cases across related Nyx surfaces.apps/oshun/web/e2e/nyx-event-ics.spec.tshas two single-event ICS download cases.apps/oshun/web/e2e/nyx-event-actions-bff.spec.tshas four BFF auth/validation/state cases; andapps/oshun/web/e2e/nyx-event-actions-real-sync.spec.tshas one browser-to-real-BFF synchronization case. Neither claims scheduling.apps/oshun/web/e2e/profile-calendar-sync.spec.tshas four Profile UI cases over mocked calendar routes;apps/oshun/web/e2e/calendar-connectors-bff.spec.tshas three cases for the separate admin connector runtime.apps/oshun/web/e2e/nyx-observations-bff.spec.tshas four owner-scoped observation API cases.apps/oshun/web/e2e/nyx-service-worker-notification.spec.tshas two injected service-worker notification cases.- The 35 named cases are partial coverage. Completion requires one automated trace with the same reconciled event identity and member from detail intent through durable reminder, provider or delivered notification receipt, event navigation, observation write, and read-back.
Per-view files touched#
customer/06-nyx/nyx-events.md— dynamic agenda and whole-feed subscription.customer/06-nyx/nyx-event-detail.md— fixed event, one-event ICS, preference controls, and observation link.customer/09-account/profile.md— provider connections and schedule export.customer/06-nyx/nyx-observation.md— local-first rich observation log and owner-scoped BFF boundary.
Cross-references#
nyx-tonight-observation.md— dynamic astronomy and the separate direct observation form.messages-quiet-hours-and-channel-binding.md— recipient verification, delivery routes, worker, and provider receipts.account-deletion-and-dsar.md— durable export/erasure versus unmanaged browser-local records.- Result:
nyx-event-calendar-sync-reminder.md— dated source and automation reconciliation.
Open questions#
- Which dynamic/fixed event mapping is authoritative for reminders, calendar links, and observations?
- Should the detail action state be migrated into the durable member reminder or removed in favor of it?
- Should agenda rows expose separate Download this event and Subscribe to all events controls?
- Which provider exports are production supported, and how will provider event ids and receipts be shown?
- Should worker delivery carry a verified push recipient and structured
actionPath, and shouldPwaBootstraproute warm taps? - How will local Nyx state be subject partitioned, cleared, replayed, and reconciled with owner-scoped server rows?
- What single trace and restart test will define completion of this journey?