Survives being used: cold-start audit remediation (16 tasks) + Slack-level surface - #1
Open
dmfarcas wants to merge 75 commits into
Open
Survives being used: cold-start audit remediation (16 tasks) + Slack-level surface#1dmfarcas wants to merge 75 commits into
dmfarcas wants to merge 75 commits into
Conversation
…ser, cmd-k, a11y Newcomer-first redesign grounded in a cited competitive study (Slack/Discord/ Teams/Element/Zulip/The Lounge) and the /design mock system. Decomposes the work into 9 workstreams, each closing with a visual+a11y+interaction parity loop. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
INDEX (roadmap, global constraints, shared Ralph loop) + WS-0 foundation, WS-1 docker, WS-2 auth, WS-3 account menu, WS-4 network directory, WS-5 channel browser, WS-6 command palette, WS-7 shell polish, WS-8 a11y sweep. ~67 TDD tasks, each UI workstream closing with the visual+a11y+interaction parity loop. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Multi-stage Dockerfile + compose + entrypoint (auto-gen secret) + README quickstart. Runtime docker-up verification deferred — sandbox Docker daemon unresponsive; files pass static checks (bash -n, Dockerfile/compose well-formed). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…v, Esc, focus restore)
Field, EmptyState, Kbd, LiveLog, Menu accessible primitives + deterministic seedDemo() on window.__bool. 283 client tests pass, typecheck clean. Follow-up fixes (Menu arrow-key test, dev-seed assertion + messages) tracked next. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ons + seeded messages, Kbd radius token, Menu unmount focus guard Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ard, pick-your-vibe)
…ite validation routes
…rsisted via setTheme)
…ste allowed, icon+text errors
…d register, paste allowed)
…yLabelText accessible name matching
…tstrap server-side
Security review found the WS-2 auth guarantees were UI-only and bypassable:
consumeInvite was dead code and sign-up reached better-auth with no invite check,
so anyone could register without an invite and a code was reusable forever; the
first-user->admin bootstrap was never implemented.
Enforce it in better-auth via databaseHooks.user.create (proven to receive the
request body incl. inviteCode as ctx.body). The before-hook:
- lets the very first account through and promotes it to role 'admin'
(re-checked at creation time against the real user table, not the UI/probe);
- for every subsequent PUBLIC sign-up (/sign-up/email) requires a valid, unused
invite and throws APIError('FORBIDDEN') otherwise — non-bypassable even via a
direct POST to /api/auth/sign-up/email;
- leaves admin-initiated creates (/admin/create-user) ungated.
The after-hook calls consumeInvite(code, user.id) to mark the invite used
(single-use, atomic single-row UPDATE).
Add POST /api/setup as the explicit server-gated first-admin route: 409 when
users already exist (re-checked at creation time). Centralize userCount() in
bootstrap-admin.ts. Remove the dead duplicate ensureInvitesSchema() (table lives
in schema.ts).
Server tests prove: first user becomes admin; second registration with no /
invalid / used code is rejected 403 (incl. the direct better-auth endpoint);
invites are single-use; POST /api/setup returns 409 when users exist. A test
helper (test/signup.ts) seeds a fresh single-use invite for non-first sign-ups so
existing multi-user tests exercise the real enforcement path.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…by/aria-invalid The custom <p role="alert"> invite error in LoginForm was not programmatically linked to the invite input. Route it through the WS-0 Field primitive's validate prop so Field wires aria-invalid + aria-describedby to its own error node. Clear the error as the user edits. Strengthen the test to assert the input's aria-describedby points at the element holding the error text. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…otstrap) Setup wizard (zero-users→admin), redesigned sign-in/register card on WS-0 Field, pick-your-vibe theme step, invites. Security enforced server-side via better-auth databaseHooks (invite-gated, single-use; first-user→admin; /api/setup 409 guard) — reviewed (opus) + re-reviewed clean. server 204 + client 301 tests pass. Follow-up (non-blocking, tracked for final review): consume-on-validate in the before-hook to close the narrow invite-reuse/first-user race; fail-closed path check. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Wraps Virtuoso in the WS-0 LiveLog primitive (role=log, aria-live=polite, aria-label=Messages). Adds [role=log] flex layout in app.css so Virtuoso retains its height. Also migrates grouping tests to MessageRow direct-render (Virtuoso does not render in jsdom), and adds empty-state + skeleton tests with fake-timer settle. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…S-0 EmptyState
Replaces inline .msgs-empty block in MainPane with EmptyState("Pick a channel to start chatting") and moves the pre-existing no-messages-yet inline div to EmptyState("No messages yet") in MessageList.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ageList Adds HistorySkeleton component (role=status, aria-busy=true) shown while waiting for first history page on channel selection (4s settle heuristic). CSS shimmer animation disabled under prefers-reduced-motion (static bg-3 fallback). Token-driven sizing via --msg-fs, --group-gap. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The Dockerfile installs deps only for client/server/shared, but COPY . . brings in packages/desktop (added in milestone 8), so 'pnpm -r build' died on the Electron package. Found by the cold-start audit: the documented 'docker compose up' path did not build at all. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
ParsedCommand gained notice/away/back/invite/names in WS-6 but the composer's dispatchInput switch was never extended: the new commands fell through (TS2366 broke the production build) and typing them cleared the input while doing nothing. Wire the five cases to the store actions. Typecheck clean; 364/364 client tests pass. NOTE: away/invite/names are still dropped server-side (no ws.ts cases) and chat:send ignores the notice flag — see docs/superpowers/audits/2026-07-17-cold-start-audit.md. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
First end-to-end use of bool on the shipping path: fresh docker volume, real signup, real TLS to Libera.Chat, two-sided messaging via a scripted probe client, container restart mid-session, mobile pass. 7 BOUNCE-class findings (DM self-keying, no WS reconnect, no session restore, mobile layout, docker build, dead commands, zero-state routing), 8 FRICTION, 6 POLISH, 3 process findings. Includes reprioritization vs WS-3/5/8. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…s silent, root cause unknown Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… CI reality gate Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…nection visibility, channel browser, previews, orientation, polish) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…by-gap against design/direction-a) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…er on connect (Task 3 review fixes)
…on boot (audit B3)
…+refresh (audit B3)
…ail selectors (audit B4)
…tion badges + honest counts (audit B7,F1-F3)
…al (audit process findings) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ns (kills slash-command dependency)
…lper (Task 8 review fixes)
… on isChannel (Task 9 review fixes)
UserPopover previously received a click-time-frozen networkId but re-derived
channel/canOp from the live `selected` store value, letting a stale popover
(opened from a message sender, then followed by a keyboard channel switch that
fires no mousedown) dispatch Kick/Ban with an empty channel. It also always
showed "Invite to channel..." even when opened from a PM, sending the PM nick
as a fake channel.
Fix: MessageList and MemberList now capture a single frozen snapshot
{networkId, channel, isChannel, canOp} at click time from the current
selected-derived state, and pass it straight through. UserPopover is now a
pure renderer of that snapshot (no more internal useChat(selected) read) and
gates Invite + all op actions on isChannel. Both hosts also close the popover
on selection change as a second guard against the keyboard-switch race.
…ad numerics console (audit F4)
- NetworkState gains state: connecting|registered|reconnecting|closed;
connected stays derived (state === 'registered') for existing consumers.
- Sidebar shows a zero-jargon status line (Connecting…/Reconnecting…/
Disconnected) with a Reconnect button on closed, calling connectNetwork(id).
- Root cause of the silent numerics/MOTD pipeline: formatNumeric() didn't
strip the leading IRCv3 message-tags blob ("@key=val;... ") that
irc-framework's raw event carries on virtually every real inbound line,
since irc-framework requests message-tags + server-time by default and
nearly all modern servers (Libera, ergo, ...) grant them. The numeric
regex never matched a tag-prefixed line, so 001-005/MOTD/LUSERS were
silently dropped. Confirmed by reading irc-framework 4.14.0 source (raw
IS proxied Connection->Client, from_server IS true) and corroborated live
against both a synthetic CAP-negotiating ircd and a real ergo instance.
…pt (audit F5) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…-pipeline test (audit F6)
…(no cast) (Task 12 review fixes)
- fetchPreview's catch blocks now capture the Error and attach an
internal-only `reason` string to PreviewResult (never added to the
zod previewResultSchema wire contract), so manager.ts's
`[preview] not ok` log carries a diagnosable reason instead of a
bare URL.
- Retype the SSRF-pinned `lookup` option directly as
`import('node:net').LookupFunction` — @types/node already models
both the array and single-address callback forms via
`options.all`, so the arity-sniffing and the
`pinnedLookup as unknown as http.RequestOptions['lookup']` double
cast (which defeated tsc on the exact function enforcing the SSRF
pin) are no longer needed. Behavior is unchanged: only the
closure-captured pre-validated IP is ever handed back.
…elp, honest boot log, ? cheatsheet landed (audit POLISH batch)
…ew joins, not reconnect replays (Task 15 review fix)
…r emoji, caps statline Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ate history-start note to channels (final review)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Survives Being Used — cold-start audit remediation
A cold-start deep audit (first real end-to-end use of bool on the shipping path: fresh
docker compose up, real signup, real TLS to Libera.Chat, a second scripted IRC client, a mid-session container restart) found the app demoed well but had a broken daily-driver spine — features "done, tests green" but wired to nothing. This branch fixes that spine and builds out the Slack-level surface, in 16 tasks, each individually reviewed (spec + quality) with a final whole-branch integration review.Audit + plan:
docs/superpowers/audits/2026-07-17-cold-start-audit.md·docs/superpowers/plans/2026-07-17-survives-being-used.mdPhase 1 — survives being used
/notice /away /invite /namesthrough the server; honor thenoticeflaghellore-sent per connection (the "reconnecting…" banner was a lie)auto_connect; resume IRC on boot;session:targetssnapshot so channels/DMs/unreads survive restart and refreshdisplay:contentswrapper defeated.app > .railselectors; fragment fixdocker-compose.ci.yml+tools/ci-smoke.mjs: builds the real image, stands up a real IRC server (ergo), and drives signup → connect → join → message → restart → world intact. This is the audit, automated.Phase 2 — Slack-level surface
UserPopover(Message/Whois/Invite + op actions) on member rows and message nicks, killing the slash-command dependency/LISTresultslookupdidn't handle Node's Happy-Eyeballs{all:true}); SSRF IP-pin preserved and now compiler-enforced?shortcut cheatsheet@mentions, code blocksVerification
net:listwipedcapson reconnect) and fixed.unitjob will be red until thepnpm-lock.yamlis synced —packages/desktopdeclareselectron-builderbut it was never added to the lockfile (pre-existing drift, milestone-8). Runpnpm install && git commit pnpm-lock.yamlin a networked env. Thesmokejob passes.Deferred Minor roll-up (none block the spine) is tracked in
.superpowers/sdd/survives-progress.md.Note: this branch also carries the earlier UX-overhaul work; the above is the audit-driven remediation layered on top.
🤖 Generated with Claude Code