Commit Graph

9 Commits

Author SHA1 Message Date
Ben Stull 8f3a8ec33a Release 0.10.0: user-set passcodes (OTC stays as fallback)
After a successful OTC sign-in, a contributor can set a passcode
(4-20 characters, bcrypt-hashed) and use email + passcode for
subsequent sign-ins. OTC remains the structural fallback: five
consecutive failed verifies lock the passcode path for 15 minutes
(HTTP 423), and a forgotten passcode is recovered by requesting a
fresh code. Migration 015_passcode.sql adds four nullable columns
to the users table; existing rows pass through as OTC-only and can
opt into a passcode from a new Sign-in tab in /settings/notifications.
The /login surface is extended to a five-step flow (email → either
passcode or OTC code → optional post-OTC passcode offer → optional
set-passcode). SPEC corrections per §19.3 rule 2: §6 names the
three auth paths, §14.1 documents the stepped login flow, §17 lists
the four new /auth/passcode/* endpoints, §19.2 surfaces four new
candidates and refreshes the cross-refs.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-28 02:24:38 -07:00
Ben Stull 8aa65014b4 Release 0.13.0: cookie/privacy consent banner + policy pages (instrumentation prep)
Roadmap item #11. Ships the non-modal bottom-of-page cookie consent
banner, the default /privacy and /cookies policy pages, the
`cookie_consent` table + two §17 endpoints for server-side persistence,
the localStorage fallback for anonymous viewers, the /settings
"Privacy & cookies" tab for revisiting the choice, and the
`frontend/src/lib/consent.js` helper that roadmap item #13's analytics
SDK (v0.15.0) will gate against. No analytics SDK ships in this release
— the consent infrastructure goes in first so the gate is already in
place. Adds SPEC §14.5 / §14.6, lists two new endpoints in §17, names
the new table in §5, and surfaces four §19.2 candidates (content-repo
file vs env-var policy, GPC / DNT headers, i18n, item-#13 dependency).
Two new optional env vars (`VITE_PRIVACY_POLICY_URL`,
`VITE_COOKIES_POLICY_URL`) — defaults render the framework's stub
pages.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-28 01:08:23 -07:00
Ben Stull f8e797ab09 Release 0.7.0: email/OTC sign-in (Gitea OAuth retained as fallback)
Replaces the Gitea OAuth gesture as the primary human-auth path
(roadmap item #5, SPEC §6.2). Users sign in by entering their email,
receiving a six-digit code via the existing SMTP layer, and entering
the code on a two-step /login surface. The Gitea OAuth callback
remains functional during migration — the new UI links to it as a
fallback for users with active OAuth sessions or older invite paths
— and is scheduled for removal in a future release once OTC adoption
is universal. Existing users are linked by email on first OTC sign-
in (gitea_id preserved); new users are provisioned with NULL
gitea_id and rely on email as the identity key. The migration
introduces backend/migrations/012_otc.sql (otc_codes table + users
schema rebuild for nullable gitea_id and a partial unique index on
email), two new endpoints (POST /auth/otc/request, POST /auth/otc/verify),
bcrypt as a new backend dependency for code hashing, and 11 new
tests in test_otc_vertical.py covering the happy path, expired and
consumed and wrong codes, the per-email rate limit, the allowlist
gate, the OAuth-era link path, fresh provisioning, and prior-code
invalidation on re-request. No new secrets are required — the
existing SECRET_KEY signs sessions and bcrypt's per-row salt covers
the code hashes.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-28 01:03:18 -07:00
Ben Stull 21743a08b1 Release 0.6.0: anonymous-write gates audit + hardening
Sweep-the-edges hardening release (roadmap item #4). Audits every
write-shaped backend endpoint to confirm each one enforces an explicit
auth.require_contributor (or stricter) gate before doing state-changing
work; adds a regression test net (test_anon_offlimits_vertical.py, 12
tests, 66 assertions) so future endpoints can't quietly ship without a
gate. The only behaviour change: GET /api/rfcs/<slug>/graduate/progress
now requires auth.require_user since the step detail (repo name, PR
number, rollback steps) isn't part of the v0.3.0 anonymous-read contract.
No schema, no env, no dependency changes; operator action is rebuild +
restart per CHANGELOG §0.6.0 upgrade steps.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-28 00:47:49 -07:00
Ben Stull c92730a737 Release 0.5.0: PR-less per-RFC discussion (contribution still requires PR)
Roadmap item #3. An RFC's main view now carries a discussion surface
distinct from PR comments and from branch chat. The substrate is the
existing threads/thread_messages tables — rows with branch_name IS NULL
scope to the RFC's main view; the schema already permitted that shape,
v0.5.0 is the first build to write it. Five new endpoints under
/api/rfcs/<slug>/discussion/..., a new RFCDiscussionPanel right-column
component used when branchParam === main, SPEC §10.10 settling
discussion-vs-contribution, and §17 listing the new routes. Notification
routing reuses the existing chat_message_in_participated_thread /
chat_reply_to_my_message event kinds with branch_name=null on the
fan-out row; a distinct event_kind is a §19.2 candidate. Anonymous
viewers can read; writes require contributor — v0.6.0's item #4 will
harden adjacent gates. No schema migration; minor bump, no operator
action required beyond rebuild and restart.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-27 23:04:52 -07:00
Ben Stull 0e1805b8ce Release 0.2.1: PRView Rules-of-Hooks fix (React #310 blank page)
PRView.jsx declared its threadsByKind useMemo after the early-return
guard for the loading state. First mount: pr is null, early return
fires, 14 hooks called. Second render: pr populated, execution
reaches the useMemo, 15 hooks — React #310, page goes blank.

Move the useMemo above the early returns and null-safe the
pr?.threads access so the hook count stays stable across renders.

Pre-existing bug in the post-Contribute-rewrite PR view; surfaced
in production by 0.2.0's graduation merge race fix enabling the
workflow that opens this code path. Patch per §20.2 — no operator
action required beyond rebuilding the frontend and restarting.
2026-05-26 08:50:48 -07:00
Ben Stull 7c3b8fc133 Contribute rewrite Phase 2: split-pane preview with mermaid
Add a rendered preview pane and split the Contribute-mode center
column. Discuss mode is now a single rendered preview (no more
read-only Tiptap mount); Contribute mode renders the CM6 raw editor
on the left and a live-updating preview on the right at 50/50, with
the existing chat + change-card panels untouched on the right.

The new MarkdownPreview component renders markdown via marked and
lazy-loads mermaid on the first encounter of a ```mermaid fence in
any rendered doc. Mermaid (~200 KB gzipped, plus dagre/graphlib
subchunks) stays out of the main bundle and is fetched only when a
diagram actually appears in the rendered content. The marked
renderer is scoped via a per-component Marked instance so the
mermaid-fence behavior does not leak to Editor.jsx / DiffView.

XSS hardening on mermaid: securityLevel: 'strict' is set explicitly
in mermaid.initialize. Verified end-to-end via a sandbox eval — a
hostile `<script>window.X=true</script>` payload inside a mermaid
fence is neutralized (no script tags in the rendered SVG, no global
side-effect, diagram still renders with the offending node label
empty).

The §8.12 selection tooltip is now sourced from window.getSelection()
inside the preview surface rather than Tiptap PM positions. The
SelectionTooltip's existing `{text, coords}` contract is unchanged;
coords come from the selection range's bounding rect. Anchor payloads
for flag threads now carry just the quote text — the PM-position
from/to fields that the Tiptap-era code attached are dropped (they
were never meaningful as durable anchors anyway; quote is what §8.12
and §8.13 specify).

Design decisions confirmed before coding (all defaults):
- PromptBar spans both panes at the bottom of the center column; it
  operates on the document, not on either pane individually.
- Start-Contributing is a hard cut — no transition animation. Phase
  6's chat-drawer collapse will redo the layout machinery anyway.
- Mermaid lazy-loads on first ```mermaid fence detected, not on
  Contribute-mode entry. Keeps the gzipped cost off any doc that has
  no diagrams.
- Below 1280px viewport, a narrow-viewport banner surfaces in
  Contribute mode. The drawer collapse that fixes this properly is
  Phase 6.

Mermaid integration is kept loose for the future authoring tool: the
placeholder DOM node carries the source as a data attribute, the
renderer takes (source) → SVG via mermaid.render, and per-block
memoization keys on source. A future authoring pane can intercept
the placeholder before SVG render without restructuring.

SPEC §8.3 updated to reflect the split-pane Contribute layout — the
smallest edit that captures the new shape.

Verification notes:
- Vite preview on :5180 — sandbox mount of MarkdownPreview confirms
  rendering, mermaid SVG output, securityLevel:'strict' XSS
  neutralization, and the window.getSelection → {text, coords}
  bridge end to end.
- Network log confirms mermaid + sub-chunks are absent from initial
  load and fetched only after first ```mermaid encounter.
- Backend was not running this session, so the manual-debounce
  save-now / accept / decline / live-preview-mirror pathways were
  not driven against a real branch; they remain verified by
  inspection. Phase 1's verification gap therefore carries forward.
- 125 backend integration tests still green.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-25 10:24:19 -07:00
Ben Stull 13d59b5d26 Contribute rewrite Phase 1: CM6 markdown source editor
Swap the Contribute-mode editing surface from Tiptap WYSIWYG to a
CodeMirror 6 markdown source editor. Discuss mode and any read-only
viewing continues to render through Tiptap.

The §8.11 manual-edit debounce now reads the raw markdown source via
CodeMirror's doc, eliminating the lossy `editor.getText()` round-trip
that RFCView.jsx flagged as a §19.2 candidate; what the contributor
typed is exactly what gets POSTed to manual flush.

The §8.10 paragraph-margin gutter accent on Tiptap is dropped — it was
dead in read-only Discuss anyway, and Phase 4 of the Contribute
rewrite adds a change-anchored gutter against the CM6 raw pane.

The Tiptap-side accept-time injection of <span class="tracked-*">
markup also drops out — CM6 can't render those spans, and Phase 3's
preview pane is the proper home for tracked changes. The reviewMode /
DiffView toggle still works against marked-rendered HTML in the
interim until Phase 7 retires it.

Notes:
- Bundle gzipped grows ~50KB for CM6 modules (state/view/commands/
  language/lang-markdown). Mermaid in Phase 2 will be the larger lift
  and should lazy-load.
- Verified the CM6 editor mounts cleanly via Vite dev preview: ref
  handle (`view/getDoc/setDoc`) is wired, `onUpdate` fires on doc
  changes, gutter / line-numbers / active-line all render, and the
  source round-trips verbatim. Could not drive the full RFCView
  Contribute flow end-to-end without a running backend; the manual
  countdown + save-now + accept/decline pathways are verified by
  inspection only.
- 125 backend integration tests still green.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-25 09:49:17 -07:00
Ben Stull 779ba6db59 Slice 1: scaffolding + propose-to-super-draft vertical
Brings the §1 bot wrapper, the §4 cache (webhook + reconciler), the
§5 schema (six numbered migrations), Gitea OAuth + §6 user
provisioning, the §7 catalog left pane, and the propose-to-merge
vertical: propose modal opens an idea PR against the meta repo, an
owner merges from the pending-idea view, the cache picks it up via
webhook or reconciler sweep, and the catalog renders the new
super-draft.

Per §1 the bot is the only Git writer; every commit, branch
creation, and PR merge carries the §6.5 On-behalf-of: trailer and
an `actions` audit row. Per §4 the cache is never written from a
user action — it's webhook+reconciler only.

Covered by `backend/tests/test_propose_vertical.py` against an
in-process Gitea simulator.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-24 04:31:11 -07:00