Milestone vibed-f05l and its seven subtasks laying out a new Rust CLI
binary that queries howlongtobeat.com and prints completion times.
Covers project init, request/response types, CLI arg parsing, the
HTTP search call, output formatting, and a validation/smoke-test pass.
No implementation yet — planning only.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Adds .claude/settings.json opting bit-by-bit into the frontend-design
plugin for future UI/design work on the pixel-art tracker.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Drop the pixelsPerCheck calibration constant that scaled units to a
fixed "perfect month" pace regardless of image size. 1 unit now equals
1 pixel directly (Nth check of the day reveals N pixels, multiplied by
that day's streak length), so bigger images genuinely take longer and
the numbers in the UI match what's actually happening. Also removes
the now-obsolete "~N days to finish" upload estimate and corrects the
About copy, which previously implied the streak multiplier applied
directly to pixel counts.
- Reward is now streak-multiplied: units per day = streakOnDay(d) × triangular(checks);
recalibrate pixelsPerCheck (÷ triangular(30)) and the upload finish-day estimate.
- Rename the "Backup" settings tab to "Data" and add a double-confirm "Wipe all data" action.
- Add a page footer disclosing authorship (Claude Code — Sonnet 5, Opus 4.8 & Fable 5).
- About page now explains exactly how pixels are earned (per-check escalation + streak multiplier).
- Allow uploading an image during first-run onboarding, not just presets.
- Update DESIGN.md, ARCHITECTURE.md, README.md to match.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add Paint by Habits, a local-only single-file HTML habit tracker that
reveals a pixel-art image as daily habits are checked off.
The browser-tab icon is a 64x64 miniature of the current artwork —
revealed pixels solid, unrevealed ones a two-tone checker — drawn into
an offscreen canvas and set as a data-URL PNG, so the tab fills in with
the art and the page still makes zero network requests.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Updating a page meant deleting it and re-creating it under the same name.
Add PUT /api/pages/:slug, which replaces an existing page's content while
keeping its URL, owner, trust flag, and created_at.
Authorized to owner-or-admin (core::origin::can_replace, same rule as
delete) *and* can_upload, so a revoked uploader can't keep pushing content
to pages they already own.
The replace can't roll back the way create does — R2 has no multi-object
transaction and the previous bytes aren't retained — so the ordering bounds
the damage instead: new objects are written before any old key is deleted (a
viewer mid-update sees old-or-new per file, never a blank page), stale keys
are pruned last and best-effort (a failure only orphans an object), and the
D1 stats are refreshed only once R2 succeeds. Design §4.6b covers the
reasoning; §5's "no page overwrite" scope cut is retired.
In the UI, the always-visible upload card becomes a modal driven by two
entry points: "+ New Page" (create) and a per-row "Update" (replace, with
the slug pre-filled and read-only).
Verified end to end against wrangler dev with local D1/R2 — create, serve,
replace, stale assets pruned, stats/trust/ownership preserved, plus the
401/403/404/400/413 paths — and the UI driven in jsdom (37 assertions).
152 unit + 19 doctests, both-target clippy clean.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Micro static-site host running entirely on Cloudflare: a Rust->wasm
Worker (workers-rs 0.7 + axum 0.8) serving user-uploaded .html/.zip
content from R2 at pages.elijah.run/<slug>/, with D1 for users/
sessions/page metadata and GitHub OAuth for login. Admins approve
uploaders, delete pages, and can mark pages/users trusted.
Security design (docs/plans/2026-07-12-pages-design.md): hosted
content is served with a CSP sandbox (opaque origin — no cookies or
storage, cannot make credentialed /api calls) unless admin-trusted;
strict slug grammar + reserved names; streaming zip validation
(traversal, bombs, size/entry/depth limits); SHA-256-hashed session
tokens in __Host- cookies; Origin checks on all mutations; upload
quotas and size caps.
Includes OpenTofu infra (R2, D1, DNS; wrangler owns the Worker
deploy), an embedded single-file management UI, deployment guide,
and 148 native unit tests + 18 doctests (clippy -D warnings clean on
native and wasm32). Deployed and verified in production.
Squash of 15 commits from pages-v0: each ticket implemented by a
Claude Sonnet agent, reviewed by a Claude Opus agent, tracked with
beans (pages-lxjz..pages-shqc).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Archive self-play beans, update shader/LLM parent beans to completed,
and add self-play chapter content to ml-self-play.md.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The Markov epic (edu-svom) incorrectly used blocked-by relationships to
reference its 10 section tasks. Replaced with proper parent-child
hierarchy: removed blocked-by entries from the epic and set parent on
each section task.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds llm-from-scratch.md stub with 14 sections (GPT-1 style: character
tokenisation, self-attention, transformer block, candle model, training
loop, sampling). Creates beans edu-32xl through edu-9sb7 for each section.
Add edu/src/ml-self-play.md with 14 stubbed sections across 5 parts:
foundations (RL, MCTS, self-play), game engine (Tic-Tac-Toe), MCTS
implementation, neural network integration, and the full self-play loop.
Create one beans ticket per section (edu-wobk through edu-brtk).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add shaders.md with 18 stub sections across 6 parts (GPU model,
wgpu setup, vertex/fragment shaders, textures, compute shaders,
going further). Create beans tickets edu-5g0l through edu-7m8d
for each section. Add # Graphics section to SUMMARY.md.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
nbd has been superseded by beans for issue tracking. All tickets were
migrated to .beans/ in the previous commits. Remove the nbd/ project
directory, its flake input, and all references.
- Remove nbd/ (source, tests, docs, beans, skills, flake)
- Remove nbd flake input and nbd package from devShell in flake.nix
- Update flake.lock to drop nbd and its transitive inputs
- Remove nbd entry from PROJECTS.md
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Simplify all issue-tracking documentation to defer to `beans prime`
rather than maintaining a parallel description of beans commands.
- CLAUDE.md: collapse task-tracking section to just `beans prime` callout
- edu/.claude/skills/work: nbd next → beans list --ready
- nbd/.claude/skills/work: nbd next → beans list --ready
- nbd/.claude/skills/triage: nbd tickets/.nbd → beans beans/.beans
- nbd/src/claude_md_snippet.md: replace nbd snippet with beans prime stub
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds src/introduction.md explaining the project (LLM-generated learning
resources, Claude Code + Opus 4.6/Sonnet 4.6, CC0 public domain, contact
email) and links it as the first entry in SUMMARY.md.
- OpenTofu config in infra/ for Pages project, DNS CNAME, and custom
domain at vibebooks.elijah.run
- justfile with build, serve, deploy, infra-init, infra-plan, infra-apply
targets
Closes 59c122
- Add GET /api/admin/verify — side-effect-free code check used by the
admin unlock flow; registered before reset-auth-code in the router
- Remove "Reset auth code" section from admin panel (UI + dead API code);
rotation is now CLI-only via `wrangler secret put ADMIN_AUTH_CODE`
- Add rotate-admin-code justfile recipe using pwgen for local key rotation
- Add pwgen to Nix dev shell
- Update OpenAPI spec with /api/admin/verify definition
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add AppState to handlers, read ADMIN_AUTH_CODE from Worker env, gate
reset-auth-code with 409 when secret is active, update tests.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Yew's Renderer::new() renders into <body>, not #app, so the flex
column layout on #app had no effect. Move display:flex and
flex-direction:column to body so .main-content{flex:1} correctly
pushes .site-footer to the bottom of the viewport.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Move src/bin/api/db/ and src/bin/api/handlers/ to src/db/ and
src/handlers/ so they compile as library modules accessible to both
the native binary and the Cloudflare Workers entry point
- Upgrade worker crate 0.5 → 0.7; add workers-api feature flag and
cdylib/rlib crate-type to Cargo.toml
- Update flake.nix: add worker-build and just to the dev shell; bump
flake.lock (nixpkgs + rust-overlay)
- Consolidate rate limit rules to one (Free plan allows only 1 rule
per zone in the http_ratelimit phase)
- Update infra/worker.tf to deploy via wrangler rather than Terraform
(Cloudflare provider v4 can't upload ES module + wasm bundles)
- Extend .gitignore to exclude *.wasm build artifacts
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
D1's exec() treats newlines as statement separators, causing multiline
CREATE TABLE statements to be truncated after the first line and return
"incomplete input: SQLITE_ERROR" on every request.
Fix: switch run_migrations() in D1Repository to use prepare(sql).run()
instead of exec(sql), which treats the full string as a single statement.
Also moves db and handlers modules from src/bin/api/ to src/ (library
modules), adds justfile with build/deploy/migrate recipes, adds
migrations/schema.sql for direct wrangler d1 execute usage, and adds
wrangler.toml for worker deployment configuration.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Remove captcha-gated disabled state on report submit button — Turnstile
does not fire a DOM input event so captcha_solved never became true; rate
limiting is handled at the WAF layer so the gate was incorrect
- Token is still read from the hidden Turnstile input at submit time if present
- Add explanatory hint below "Enter Auth Code" heading in AuthModal explaining
the code was provided or generated when the quote was created
- Add .auth-modal__hint CSS class for the hint text
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Derive PartialEq on ReportSummary, ReportListResponse, ReportRow, and
QuoteReports so they can be held in Yew state enums that require PartialEq
- Remove unused ApiError import from moderation_tab.rs
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds a Moderation tab to the admin page (visible after unlock) showing a
paginated list of reported quotes. Clicking a row opens a detail modal with
the full quote, all individual reports, and action buttons to delete the
quote, hide it, or dismiss the reports.
- api.rs: add ReportSummary, ReportListResponse, ReportRow, QuoteReports types
and five admin_* async functions for the reports endpoints
- components/moderation_tab.rs: new ModerationTab function_component with
paginated list, row-click loading state, and an inline detail modal
- components/mod.rs: expose moderation_tab module
- pages/admin.rs: introduce AdminTab enum and tab bar; wrap existing settings
content in the Settings tab; add Moderation tab rendering ModerationTab
- style.css: add styles for admin tabs, moderation list table, and detail modal
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add HideAuth action variant to the Action enum
- Show yellow badge when quote.hidden is true
- Add Hide/Make Public toggle button in the actions bar
- Reuse AuthModal for the hide/unhide auth prompt
- Call POST /api/quotes/:id with { hidden: !current } and X-Auth-Code
- Update quote state on success; re-prompt on 403
- Add .page-quote__hidden-badge CSS styles
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds a Report button to /quotes/:id that opens a modal containing:
- Optional reason textarea (max 256 chars) with live character counter
- Cloudflare Turnstile CAPTCHA widget (always-passes test sitekey as const)
- Submit button disabled until CAPTCHA token is non-empty
- Cancel button
On submit POSTs { reason?, captcha_token } to POST /api/quotes/:id/report.
Shows a success banner on 201 or an error via ErrorDisplay on failure.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add a toggle button ("Filters ▼ / ▲") above the quote list on the
browse page. Filter controls are hidden by default and expand when
clicked. Each filter (Author, Tag, Date range) is on its own labelled
row with consistent styling. Existing API query logic is unchanged.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Mark admin moderation endpoints ticket as done following successful
implementation and test pass.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>