# Phase 3 — status

ARCHITECTURE.md §8 Phase 3 scope is "Client Polish, Desktop & PWA." This
records what's actually built and verified in this session versus what's
still open, honestly — matching PHASE2_STATUS.md's standard.

## Built and verified live

- **Tauri desktop shell actually compiles and runs** (Feature 8.x): the
  shell was scaffolded in Phase 0 and never built until this session.
  Verified twice — once with the base plugins (`cargo check`, then full
  `tauri dev` with a stable running process confirmed via `Get-Process`),
  and again after adding `keyring`, `tauri-plugin-deep-link`, and
  `tauri-plugin-notification`. No screenshot tool is available in this
  environment, so verification is process-health (PID exists, no crash,
  plausible memory footprint), not visual — noted here rather than
  overclaiming.
- **Desktop PlatformAdapter** (§4.9): one `SecureStore` / `Notifier` /
  `BadgeSetter` / `DeepLinks` interface, three runtime implementations
  (web/PWA via IndexedDB + Notification API, Tauri via OS keychain +
  native plugins), selected at runtime via `"__TAURI_INTERNALS__" in
  window` in `createPlatformAdapter()`. The shared bundle lives in
  `apps/web` per ADR-005/ADR-009 (not `apps/desktop`), so the Tauri-only
  npm packages (`@tauri-apps/api`, `@tauri-apps/plugin-deep-link`,
  `@tauri-apps/plugin-notification`) were added there.
  - **Keychain storage**: three Rust `#[tauri::command]`s
    (`keychain_get`/`set`/`delete`) wrapping the `keyring` crate under
    service `com.slackwsh.app`, exposed to the web bundle as
    `KeychainSecureStore`.
  - **Tray/dock badge — simplified, not the real thing**: `TauriTitleBadgeSetter`
    prefixes the window title with the unread count instead of a native
    tray/dock badge. Feature 11.2 (true native badge) is explicitly **not**
    implemented — this is a documented stand-in, not an oversight.
  - **Deep links**: `slackwsh://` scheme registered in `tauri.conf.json`
    and capabilities; `TauriDeepLinks` wraps the plugin, `HistoryDeepLinks`
    (popstate-based) covers the web/PWA case.
  - **Single-instance + window-state**: `tauri-plugin-single-instance` and
    `tauri-plugin-window-state` wired into `lib.rs`.
- **Message search** (6.1): Postgres GIN full-text index
  (`to_tsvector('simple', text)`, applied via `libs/data/sql/search.sql`
  in the migration runner), `parseSearchQuery()` for `from:`/`in:`
  modifiers, `GET /workspaces/:id/search?q=` scoped to the requesting
  user's actual channel memberships (no cross-channel leakage). This is
  the ADR-007-documented Postgres fallback — Typesense is still not
  wired up.
- **Command palette / quick switcher** (6.4, 12.7): Ctrl/Cmd+K global
  shortcut, instant client-side channel-name filtering plus 250ms-
  debounced message search, dispatches/listens for a
  `slackwsh:open-palette` window event so other UI (search boxes) can
  trigger it too. Verified live in-browser.
- **Accessibility pass**: aria-labels on every icon-only button and
  otherwise-unlabeled input across `/channel`, `/workspace`,
  `/workspaces`, and `/login`; `aria-pressed` on the login mode toggle;
  `role="status"` + `aria-label` on presence-dot indicators. Not a full
  audit (no screen-reader or axe-core pass), but the obvious gaps in the
  redesigned UI are closed.
- **Web + desktop notifications** (7.1–7.3): `getCurrentUserId()`
  (client-side JWT `sub` decode, UI convenience only — never an
  authorization decision) gates the `message:created` socket handler so
  a user is never notified about their own message; notification fires
  only when `document.hidden` is true. Caught and fixed a real
  stale-closure bug before shipping: the handler closed over `members`
  state from `useState`, which is populated asynchronously by a separate
  API call in the same effect, so the closure always saw `[]`; fixed with
  a `membersRef` kept in sync alongside every `setMembers()` call.
  **Verified live end-to-end with two real accounts**, not just build +
  code review: created a second user (`notify-tester@example.com`),
  joined it to the same workspace/channel as the existing test account,
  backgrounded the existing account's tab (confirmed `document.hidden ===
  true` via direct JS execution), sent a message from the second
  account's tab, and confirmed on the backgrounded tab that the message
  rendered, zero console errors were thrown, and the full chain —
  `getCurrentUserId()` → `membersRef.current` author lookup →
  `WebNotifier.notify()` — executed without an exception. The test
  browser's `Notification.permission` was `"denied"`, so no actual OS
  toast displayed; `WebNotifier.notify()` correctly no-ops on a denied
  permission rather than throwing, which is exactly the code path this
  confirmed. The Tauri desktop `Notifier` implementation (native
  `tauri-plugin-notification`) has not been live-tested the same way —
  it typechecks and follows the same interface, but no OS toast has
  actually been observed firing from the desktop shell.

## Not built — real scope, not oversights

- **Feature 11.2 (true native tray/dock badge)**: as above, a window-title
  prefix stands in for it. Real implementation needs per-platform tray
  icon (Windows) / dock badge (macOS) plugin work.
- **Auto-update signing**: Tauri's updater is not configured at all — no
  signing keys generated, no update manifest/endpoint. Desktop builds are
  unsigned, unversioned artifacts for local testing only.
- **Real PWA install testing**: the manifest and service worker exist
  from Phase 0 but installing the PWA (as opposed to running it in a
  regular browser tab) has never actually been exercised in this session
  or before it.
- **Performance budgets in CI**: nothing enforces the composer / message-
  list performance budgets ARCHITECTURE.md references; no Lighthouse or
  bundle-size gate exists in CI.
- **Desktop OS notification live-test**: as noted above, code-complete
  and typechecked but not confirmed firing from an actual Tauri window
  (would need a native environment where OS notification permission can
  be granted and observed, not just the headless-ish test browser used
  here).
- **The verification suite** (§6/ADR-011 — property-based tests,
  fault-injection harness, TLA+ model): still open, carried over
  unchanged from Phase 2's status. Nothing in Phase 3 touched this gap.

## Test artifacts left in the dev database

A second real user (`notify-tester@example.com` / username
`notifytester`) was created to live-test cross-account notifications and
added to the same workspace/channel as the primary test account. Left in
place intentionally — it's a real, working second account useful for
future manual testing, not leftover junk to clean up.
