Skip to content

Claude/resolve merge conflicts z e sy o#7

Merged
wparad merged 80 commits into
mainfrom
claude/resolve-merge-conflicts-zESyO
May 19, 2026
Merged

Claude/resolve merge conflicts z e sy o#7
wparad merged 80 commits into
mainfrom
claude/resolve-merge-conflicts-zESyO

Conversation

@wparad
Copy link
Copy Markdown
Contributor

@wparad wparad commented May 15, 2026

No description provided.

claude added 17 commits May 15, 2026 14:11
Already implemented in AppSidebar.vue — dropdown button, account list
popover, chevron, click-outside dismiss, active account checkmark.

https://claude.ai/code/session_017L42eZnAdHChzFwEjKR7Rb
- vite.config.ts: explicitly set assetsDir + rollupOptions output paths
  so all JS/CSS/fonts/images land in dist/assets/ with content hashes
  regardless of future Vite defaults changing
- deploy-website.ts: add explicit favicon.svg rule (1-day cache on main)
  since it has no content hash and shouldn't get immutable treatment;
  assets/* regex already correctly maps to immutable on main

CloudFront: /assets/* needs a dedicated behavior in the backend infra
(rhosys/ses-email-adapter deploy/) pointing to the same S3 origin with
a 1-year TTL cache policy.

https://claude.ai/code/session_017L42eZnAdHChzFwEjKR7Rb
- SupportPanel.vue: slide-over with three tabs (Knowledge base, Contact
  us, Status); searchable 8-article local knowledge base; contact form
  with category/subject/description that opens a pre-filled mailto: link
  with auto-attached account ID, user ID, route, browser, and resource ID
- useSupportPanel.ts: module-level singleton ref shared between sidebar
  and mobile header so both buttons control the same panel
- AppSidebar.vue: '?' button above profile link, hidden on mobile
- AppLayout.vue: '?' button in mobile header (sm:hidden on desktop),
  renders <SupportPanel> at root so it overlays the full viewport

https://claude.ai/code/session_017L42eZnAdHChzFwEjKR7Rb
getUserIdentity() is synchronous and returns JWT claims directly (sub
field = user ID). No need for the async getUserProfile() +
linkedIdentities[0].connection.userId indirection.

https://claude.ai/code/session_017L42eZnAdHChzFwEjKR7Rb
aws-architect only sets aws.config.region from apiOptions.regions when
the global region is unset. When configure-aws-credentials had eu-west-1
as its default, aws.config.region was already set and our
regions: ['eu-central-1'] was silently ignored, causing NoSuchBucket.

Calling AWS.config.update({ region }) before instantiation ensures the
correct region is always used regardless of the env default.

https://claude.ai/code/session_017L42eZnAdHChzFwEjKR7Rb
Replaces the explicit AWS.config.update('eu-central-1') with the same
pattern the backend uses: read AWS_REGION from the environment and pass
as regions: [region]. configure-aws-credentials sets AWS_REGION in CI;
'eu-central-1' is the fallback for local runs without env configured.

https://claude.ai/code/session_017L42eZnAdHChzFwEjKR7Rb
…on bar

- QuarantineFilters: sender input grows full-width on mobile (w-full sm:w-48),
  wrapper switches to flex-1 on narrow screens, date inputs and clear button
  get min touch height (py-1.5, min-h-[36px])
- RuleEditorView: test-rule grid uses sm:grid-cols-2 so inputs stack on mobile
- BulkActionBar: label form wraps (flex-wrap) and input expands full-width on
  mobile (w-full sm:w-auto)
- viewports.ts: add 390×844 mobile viewport (iPhone 14)
- playwright.config.ts: add mobile project using iPhone 14 device preset
- layout.responsive.test.ts: add no-overflow and element-fit assertions for
  quarantine, rules, settings, and inbox routes

https://claude.ai/code/session_017L42eZnAdHChzFwEjKR7Rb
Touch targets: bump all action buttons and form inputs from py-1 to py-1.5
(QuarantineRow allow/reject/rule links, BulkActionBar archive/label, StatusTabs
tabs, RuleEditorView condition selects/inputs/add-action, SettingsView recheck
and role select)

Layout: LabelsView view-form grid stacks to single column on mobile
(grid-cols-2 → sm:grid-cols-2); OnboardingView domain and sender forms get
flex-wrap so the submit button drops below the input on very narrow screens;
ProfileView passkey form gets flex-wrap

Overflow: AuditLogView event type and resource ID get min-w-0 + truncate so
long monospace IDs don't blow out the row; SignalCard plain-text body gets
break-words so long unspaced lines wrap instead of overflowing the panel

https://claude.ai/code/session_017L42eZnAdHChzFwEjKR7Rb
Inbox (/):
  ?tab=archived|all reflects the active status tab; default (active) uses
  clean URL. Tab is restored on mount so /?tab=archived is a valid bookmark.

Quarantine (/quarantine):
  ?sender=, ?after=, ?before= reflect active filters; the filter watch now
  also calls router.replace so every filter change updates the URL atomically
  with the refetch. Filters are hydrated from URL on mount.

Settings (/settings):
  ?tab=emails|domains|forwarding|team|notifications reflects the active tab
  (default account uses clean URL). /settings/notifications now redirects to
  /settings?tab=notifications instead of rendering a duplicate route.

Labels (/labels):
  ?tab=views reflects the active tab; default labels uses clean URL.

Audit log (/audit-log):
  ?cursor= is updated after every page load (initial or Load-more).
  Navigating to /audit-log?cursor=X starts the log from that cursor position,
  so any page in the log is deep-linkable.

https://claude.ai/code/session_017L42eZnAdHChzFwEjKR7Rb
…o check

LabelsView tab buttons had multi-statement @click expressions that Prettier
reformatted to newline-separated form — valid JS but rejected by Vue's template
compiler. Extracted into a selectTab() method so the handler is a single call.

Added vite build to the check script so template parse errors are caught
locally (vue-tsc --noEmit doesn't exercise the Vite plugin chain, so errors
like this slipped through to CI).

https://claude.ai/code/session_017L42eZnAdHChzFwEjKR7Rb
…, changelog, titles

Bugs fixed:
- SettingsView saveNotifications() was sending {} to the API; now sends the
  full notifications.email object (enabled, address, frequency)
- updateAccount() body type extended to include NotificationSettings
- BillingView "Open billing portal" button wired to VITE_BILLING_PORTAL_URL
  env var (shows configuration hint when unset); loads real account data
  instead of hardcoded "Pro" plan text

Navigation:
- /changelog route added with ChangelogView listing v0.1.0 changes;
  SupportPanel router-link now resolves correctly
- Catch-all 404 route added; NotFoundView with back-to-inbox button

Cleanup:
- Deleted src/stores/onboarding.ts (defined but never imported anywhere;
  OnboardingView manages its own local state)

UX:
- router.afterEach sets document.title per route so browser tabs and
  history entries are distinguishable

https://claude.ai/code/session_017L42eZnAdHChzFwEjKR7Rb
… TODOs

- CI was configuring AWS credentials for eu-central-1 but all infra (S3
  bucket, CloudFront) lives in eu-west-1 — deploy-website.ts inherits the
  credentials region, so uploads landed against the wrong endpoint and got
  NoSuchBucket. Pass aws_region: eu-west-1 to the setup action in both
  the build and cleanup jobs.
- Replace the catch-all 404 route with a redirect to / so unknown paths
  always land on the inbox rather than a dead-end page.
- Add a consolidated "Backend routes the frontend calls" section to
  TODO.md listing every unimplemented /accounts/* endpoint so they can
  be ported to rhosys/ses-email-adapter.

https://claude.ai/code/session_017L42eZnAdHChzFwEjKR7Rb
The state bucket happens to be named with eu-west-1 but the deployed
S3 site bucket is in the default region (eu-central-1). The prior
commit incorrectly changed both jobs to eu-west-1.

https://claude.ai/code/session_017L42eZnAdHChzFwEjKR7Rb
Outputs the bucket name and CF distribution ID read from OpenTofu
state, the active AWS region env vars, the caller identity (to confirm
which account/role is assumed), and a head-bucket check so we can see
whether the bucket exists and is accessible from CI.

https://claude.ai/code/session_017L42eZnAdHChzFwEjKR7Rb
… and BillingView redesign

- src/types/server.ts: add BillingPlan, BillingStatus, BillingInfo types
- src/lib/api.ts: add createAccount, getBilling, createCheckoutSession, createBillingPortalSession; import BillingInfo
- src/stores/account.ts: add fetched flag, createAccount action (stores new account, updates session/local storage)
- src/router/index.ts: mark onboarding requiresAuth, add account-check guard that redirects to onboarding when no account exists
- src/views/OnboardingView.vue: rewrite as 6-step wizard (Account, Domain, Test email, Sender, Filter mode, Done); skips account step for returning users
- src/views/BillingView.vue: full redesign with plan tier grid (Free/Starter/Pro), Stripe checkout redirect, billing portal session, success banner
- TODO.md: clarify POST /accounts is needed for self-service onboarding; add POST /billing/portal-session to backend route list

https://claude.ai/code/session_017L42eZnAdHChzFwEjKR7Rb
Prettier now runs as a separate non-blocking step (continue-on-error: true)
so formatting issues surface as warnings without failing the build. Removed
it from the blocking `check` script — the existing `format:check` script
remains for local use.

Debug step additions:
- `tofu output -json` dumps ALL available OpenTofu outputs so we can see
  exactly what keys exist and what values they hold
- `aws s3api list-buckets` lists the first 20 buckets in the account so
  we can cross-check the expected bucket name against what actually exists

https://claude.ai/code/session_017L42eZnAdHChzFwEjKR7Rb
The Terraform state stores the bucket name with eu-west-1 baked in but
the actual bucket lives in eu-central-1. Pipe the tofu output through
sed to correct the region before writing to GITHUB_ENV. Applied to both
the build and cleanup jobs.

https://claude.ai/code/session_017L42eZnAdHChzFwEjKR7Rb
@github-actions
Copy link
Copy Markdown

Preview deployed

URL: https://email.rhosys.cloud/pr/claude-resolve-merge-conflicts-zesyo/

Deep links require the CloudFront path-rewrite rule to be configured for the /pr/claude-resolve-merge-conflicts-zesyo/ prefix.

claude added 12 commits May 15, 2026 19:17
Removes VITE_STRIPE_PRICE_STARTER / VITE_STRIPE_PRICE_PRO env vars
from BillingView — price IDs are now hardcoded TODO placeholders to be
replaced with real Stripe price IDs once the account is set up.

https://claude.ai/code/session_017L42eZnAdHChzFwEjKR7Rb
LoginView was calling authenticate() unconditionally on every mount,
including when Authress redirected back after a successful login. Since
no redirectUrl was set, Authress returned to /login, remounting the
view and restarting the flow.

Fix: call userSessionExists() first, which processes any OAuth callback
params in the URL. If a session is established, redirect to /. Only
call authenticate() when there is genuinely no session, and set an
explicit redirectUrl pointing to the app root so Authress never returns
to /login.

https://claude.ai/code/session_017L42eZnAdHChzFwEjKR7Rb
Every empty state now has a bold primary headline and a sentence
explaining what the section does and how to get started:

- Inbox (active/archived/all): explain DNS setup requirement / archive purpose
- Quarantine: explain filter mode and the approve/block workflow
- Search: suggest alternative search terms and fields
- Settings → Aliases: explain per-address filter modes
- Settings → Domains: explain DNS verification requirement
- Settings → Forwarding: explain how forwarding addresses tie into rules
- Reply composer: clarify verified domain requirement with a settings link

https://claude.ai/code/session_017L42eZnAdHChzFwEjKR7Rb
Across inbox, quarantine, search, settings (aliases, domains, forwarding, team),
rules, labels, and views — replaced flat descriptive text with copy that frames
the value of taking the next step: loss framing, friction-removal cues ("usually
within minutes", "one click away"), and clear forward motion.

https://claude.ai/code/session_017L42eZnAdHChzFwEjKR7Rb
Router guard now passes the original path as ?redirect= when bouncing
unauthenticated users to /login. LoginView uses it as the Authress OAuth
callback URL so the user lands at the correct page after authenticating,
rather than always hitting the inbox root.

https://claude.ai/code/session_017L42eZnAdHChzFwEjKR7Rb
Previously passed the destination path directly as the OAuth callback URL,
which would fail unless every deep-link is a registered redirect URI in
Authress. Now uses window.location.href so Authress returns to
/login?redirect=<destination>, and LoginView handles the final navigation.

https://claude.ai/code/session_017L42eZnAdHChzFwEjKR7Rb
- Store keeps quarantine_visible and quarantine_hidden in separate arrays
  instead of merging them; each bucket is sorted by receivedAt desc
- fetchMore exhausts all visible pages before loading hidden pages,
  preserving the visible-first ordering across pagination
- QuarantineView renders two labelled sections ("Needs review" /
  "Silently held") with a single Load more button at the bottom
- Updated quarantine store tests to cover the new bucket API and
  the visible-before-hidden fetchMore ordering

https://claude.ai/code/session_017L42eZnAdHChzFwEjKR7Rb
visibleItems/hiddenItems renamed to quarantineVisible/quarantineHidden,
mirroring the quarantine_visible/quarantine_hidden status values from the
API. Also fixes RuleEditorView which referenced the removed .items property.

https://claude.ai/code/session_017L42eZnAdHChzFwEjKR7Rb
- EmailTemplate type in server.ts
- API methods: list, create, update (PUT), delete
- useTemplatesStore (Pinia) with loading/error state
- TemplatesView at /templates: list, inline editor with edit/preview
  tabs, Handlebars variable chip palette, live preview via marked
- Templates sidebar link between Rules and Labels
- Rule editor: auto_reply/auto_draft actions now use a template
  dropdown instead of a raw ID input, with a "Manage" link to /templates
- Updated RuleEditorView test to mock listTemplates and fix stale
  quarantine store reference (items → quarantineVisible/quarantineHidden)

https://claude.ai/code/session_017L42eZnAdHChzFwEjKR7Rb
Expands the email templates backend section with the full EmailTemplate
resource shape, supported interpolation variables, exact endpoint
signatures (request/response bodies, status codes), and the rule
integration behaviour for auto_reply/auto_draft.

https://claude.ai/code/session_017L42eZnAdHChzFwEjKR7Rb
Templates now support named JS functions alongside static sender variables.
Each function receives (signal, arc) and returns a string; outputs are
referenced in the body/subject as {{fn.name}}.

- TemplateFunction type added to server.ts
- EmailTemplate.functions field added
- API and store bodies updated to include functions
- Template editor: function list with name + code textarea per entry,
  add/remove controls, default starter expression
- Preview runs functions in an inline Worker (sandboxed from main thread),
  shows resolved fn.* outputs alongside the rendered HTML
- Static variable chips replaced with inline label showing available tokens
- TODO updated with full backend contract: resource shape, security
  requirements (sandboxed VM), and render pipeline for auto_reply/auto_draft

https://claude.ai/code/session_017L42eZnAdHChzFwEjKR7Rb
claude added 29 commits May 16, 2026 19:18
- useToast composable: module-level singleton with showUndo (action done,
  undo reverses) and deferAction (action pending, timeout commits)
- ToastStack component: fixed bottom-right, SVG countdown ring draining in
  real time, prominent mauve Undo/Cancel button, springs in on entry
- Dynamic send window via undoWindowMs(): <50 words → 10s, 50–199 → 1min,
  200–499 → 3min, 500+ → 5min
- DraftSignalCard: send is now deferred — clicking Send queues the email and
  shows a countdown toast; Cancel send (in toast or in card) aborts the send
- ArcDetailView: archive shows an 8s undo toast; undo PATCHes the arc back
  to active and reloads the thread
- Mark toast/undo task done in TODO.md

https://claude.ai/code/session_017L42eZnAdHChzFwEjKR7Rb
Send is now called immediately on click; the toast countdown gives the
user a window to cancel. Cancelling PATCHes the signal status back to
'draft' — if the backend reports the email already delivered, the card
emits 'sent' and surfaces an error message instead.

Adds api.patchSignal (PATCH /accounts/:id/signals/:id) alongside the
existing send and delete helpers.

https://claude.ai/code/session_017L42eZnAdHChzFwEjKR7Rb
Replaces all plain "Loading…" text across InboxView, ArcDetailView,
QuarantineView, RulesView, TemplatesView, LabelsView (×2), AuditLogView,
BillingView, SettingsView (×4), and ProfileView (×2) with animate-pulse
placeholder rows that match the shape of the real content.

https://claude.ai/code/session_017L42eZnAdHChzFwEjKR7Rb
Inbox zero: CSS fireworks overlay fires once when the active tab transitions
from items to empty (never on initial load). Auto-dismisses after 3.4 s with
a spring card entrance/exit transition.

Feature tour: spotlight-based 5-step walkthrough (Inbox → Quarantine →
Rules → Labels → Settings) using a box-shadow cutout on data-tour anchors in
AppSidebar. Auto-starts after onboarding completes on first login.
Permanently dismissed by PATCH onboarding.featureTourCompleted on the account.
Re-triggerable via a "Start tour" button on the Profile page.

https://claude.ai/code/session_017L42eZnAdHChzFwEjKR7Rb
Toast window is now fixed at 30 s regardless of email length — the
dynamic window was impractical for a global notification bar.

After the toast expires (or is dismissed), user-sent signals show an
"Undo send" footer button inside the expanded SignalCard that PATCHes
the signal status back to draft. On success the thread re-fetches so
the draft card reappears; on failure (already delivered) an inline
error is shown.

ArcDetailView now re-fetches on sent, discarded, and undo events so
the thread stays in sync with backend state in all cases.

https://claude.ai/code/session_017L42eZnAdHChzFwEjKR7Rb
- ArcRow: expandable tree row with lazy-loaded child SignalRows, hover-revealed Reply + Archive buttons, expand chevron
- SignalRow: new compact signal row with overflow menu (View Original Email, Undo send)
- SignalCard: replace bottom undo bar with overflow menu (View Original Email, Undo send)
- ArcDetailView: move Archive + Reply buttons to arc header; remove standalone bottom Archive button
- arcs store: add removeArc() for optimistic removal after single-arc archive
- ArcRow test: add setActivePinia/createPinia in beforeEach for new store dependencies

https://claude.ai/code/session_017L42eZnAdHChzFwEjKR7Rb
…clone

- InviteView: /invite?inviteId=<id> unauthenticated route; extracts inviteId,
  checks for OAuth error params, calls userSessionExists() then authenticate()
  with categorized error messages (expired / already used / not found / invalid)
- Error tracking: logger.ts (relay POST to relay.rhosys.ch), buildInfo.ts,
  environment.ts, analytics.ts (PostHog init with session recording + masking);
  Vue errorHandler + unhandledrejection + router.onError + beforeunload flush
  all wired in main.ts; set VITE_POSTHOG_KEY + VITE_POSTHOG_HOST to enable
- vite.config.ts: inject VERSION_INFO and DEPLOYMENT_INFO globals at build time
- TemplatesView: Clone button opens editor pre-filled with "Copy of <name>"
- posthog-js added as dependency

https://claude.ai/code/session_017L42eZnAdHChzFwEjKR7Rb
Flow: after user views the test arc (or 20s passes), a floating card
slides up in the bottom-right showing an incoming-email-style message.

- Step 1: asks to enable browser notifications; if granted fires a demo
  notification so the user immediately sees how it works
- Step 2: offers to start the feature tour right now; clicking Start
  tour dismisses the coach and launches the existing walkthrough

InboxView arms the 20s timer on unmount (user leaving for arc-detail)
and fires the coach immediately on remount (user returning to inbox).
The timer in useOnboardingCoach is module-level so it survives the
InboxView unmount/remount cycle.

AppLayout now gates tour auto-start behind notificationCoachCompleted
so the coach always runs first for fresh users; returning users who
already completed the coach but skipped the tour still get it on next
load.

- useOnboardingCoach.ts: arm/trigger/dismiss singleton
- OnboardingCoach.vue: Teleport'd floating card with 2-step flow
- OnboardingState: add notificationCoachCompleted flag

https://claude.ai/code/session_017L42eZnAdHChzFwEjKR7Rb
armed → countdownRunning
arm() → startArcViewCountdown()
trigger() → showCoachNow()
dismissCoach() → hideCoach()
coachActive → coachVisible

https://claude.ai/code/session_017L42eZnAdHChzFwEjKR7Rb
- New `src/stores/shortcuts.ts`: flat binding list persisted to localStorage
  under `ses-ui.shortcuts`. Five presets: Default (Gmail-style), Gmail, Superhuman,
  Spark (Readdle), and Fastmail — capturing the major web email client conventions.
  `loadPreset()` populates the full binding list; individual `setBinding()` overrides.

- New `src/composables/useKeyboardShortcuts.ts`: module-level singleton registers one
  `document.keydown` capture-phase listener. Handles two-key g→X sequences (500 ms
  window), guards against firing inside inputs, exposes `onAction`/`offAction` for
  per-view handler registration, `setCapturing` for key-record mode, `setBlocked`
  to suppress shortcuts while the help overlay is open.

- New `src/components/ShortcutHelpOverlay.vue`: `?`-triggered modal (Teleport'd,
  v-model:open). Preset chips let users load any scheme with one click. Grouped
  shortcut table with per-row Edit / clear buttons. Edit mode captures the next
  keypress, shows conflict warnings with "Save anyway" option that nullifies
  conflicting bindings. Footer has "Reset all to defaults" button.

- `InboxView`: j/k focused-arc navigation with scroll-into-view, Enter/o opens,
  e archives, x toggles selection. `focusedArcId` passed to ArcList.

- `ArcList`: new optional `focusedArcId` prop forwarded to ArcRow.

- `ArcRow`: new optional `focused` prop adds `ring-1 ring-inset ring-ctp-mauve`
  highlight; outer div gets `data-arc-id` for querySelector scroll lookup.

- `AppLayout`: calls `initShortcuts()` on mount, registers global go-to and search
  handlers, adds `<ShortcutHelpOverlay>`, blocks shortcuts when overlay is open.

- `ProfileView`: "Keyboard shortcuts" card with Customize button that opens the overlay.

https://claude.ai/code/session_017L42eZnAdHChzFwEjKR7Rb
Warns via ::warning:: annotations if any JS chunk exceeds 400 KB or the
total JS bundle exceeds 1 MB. Uses continue-on-error so the build always
proceeds regardless.

https://claude.ai/code/session_017L42eZnAdHChzFwEjKR7Rb
When a new arc or signal event arrives over the SharedWorker WebSocket,
fire a Notification() if permission is granted and the arc urgency clears
the threshold derived from the user's email digest frequency preference:
- instant (or unset): critical, high, normal
- hourly: critical, high only
- daily: critical only
- low / silent: never notify

Notification title reflects urgency (🚨 Critical / ⚠️ High / New email).
The `tag` field deduplicates OS-level notifications for the same arc.

https://claude.ai/code/session_017L42eZnAdHChzFwEjKR7Rb
Browser notifications fire purely on arc urgency:
critical / high / normal → notify, low / silent → skip.
The email digest frequency is unrelated to browser notification behaviour.

https://claude.ai/code/session_017L42eZnAdHChzFwEjKR7Rb
Adds src/types/realtime.ts with a discriminated union of all server event
shapes. Each event type carries just enough for useful notifications and
store invalidation — IDs, urgency, from, subject — without the full entity.

useRealtime.ts now narrows on event.type via a switch, so fireNotification
only handles signal:created and arc:created (the only events with urgency
and sender info), and the switch exhausts all known event types cleanly.

https://claude.ai/code/session_017L42eZnAdHChzFwEjKR7Rb
src/types/realtime.ts: replace 9 CRUD events with just two —
  signal:created (arcId, signalId, urgency, from, subject)
  arc:updated    (arcId only — client re-fetches)

useRealtime.ts: remove unused quarantine/rules store imports, tighten
handleEvent switch, fireNotification now takes SignalCreatedEvent directly
with no type narrowing needed.

TODO.md: add backend WebSocket event requirements so the server knows
exactly what payload each event must carry.

https://claude.ai/code/session_017L42eZnAdHChzFwEjKR7Rb
…ist reload

arcs store: add refreshArc(arcId) — fetches one arc via api.getArc() and
upserts it into the items list (updates in place if present, prepends if new).

useRealtime: on signal:created or arc:updated, call arcsStore.refreshArc()
for a targeted inbox update. If the signals store currently has that arc's
detail open (signalsStore.arc?.id === event.arcId), also call
signalsStore.fetchAll() to pull the arc + its recent signals together.

https://claude.ai/code/session_017L42eZnAdHChzFwEjKR7Rb
Router guard was redirecting all non-onboarding routes back to /onboarding
when onboarding.completed was false — including profile and billing.
Exempt those two routes so authenticated users can always reach them.

https://claude.ai/code/session_017L42eZnAdHChzFwEjKR7Rb
- UserAvatar hover popup now has a Sign out button alongside View full profile
- .env.production sets VITE_POSTHOG_KEY and VITE_POSTHOG_HOST for prod builds
- analytics.ts: derive ui_host from host — proxy ingestion URLs keep the
  PostHog dashboard UI pointing at eu.posthog.com

https://claude.ai/code/session_017L42eZnAdHChzFwEjKR7Rb
Remove hardcoded VITE_POSTHOG_HOST from .env.production; pass it via the
Build step env using secrets.POSTHOG_URL so the value never lives in the repo.

https://claude.ai/code/session_017L42eZnAdHChzFwEjKR7Rb
- Browser push notifications: mark [x] — frontend complete, blocked on backend WS
- Remove: CSV export, import/export rules/templates/labels, changelog badge
- Add backend TODOs: Webhooks UI and API keys management
- Webhooks/API keys frontend items remain open pending backend

https://claude.ai/code/session_017L42eZnAdHChzFwEjKR7Rb
- tests/e2e/a11y.test.ts: audit 6 main routes with wcag2a + wcag2aa tags;
  stubs auth + API so tests run without a live backend; failure message
  prints violation ID, description, and target selectors
- TODO: note to extend to full route list + wcag21 tags once views are stable
- TODO: JMAP backend spec (RFC 8620/8621) with frontend adapter flag

https://claude.ai/code/session_017L42eZnAdHChzFwEjKR7Rb
Bundle-size CI hardening, axe-core route expansion, test coverage backfill,
modular component system, and AI rule action are all deferred until after
the app ships. Added a clear V2 section with that rationale.

https://claude.ai/code/session_017L42eZnAdHChzFwEjKR7Rb
@wparad wparad merged commit 9a96b34 into main May 19, 2026
2 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.

2 participants