F1
Phase 1 - Marketing Website
Building · Started 2026-07-03 · awaiting design-picker selection for final page build

What this feature is

AstraRad is a teleradiology service: hospitals and clinics send imaging studies (X-ray, mammography, ultrasound, CT, MRI, nuclear medicine, PET-CT), our AI writes a preliminary pre-read in minutes, and a subspecialist radiologist signs the final diagnostic report. Customers pay per report.

This feature is the public marketing website, the front door that converts hospital imaging directors into quote requests. It shows the three things the reference competitor (LMVP Telemedicine) hides: the product (AI pre-read workflow), the process (3-step how-it-works), and the pricing model (per-report, no contracts; numbers stay private behind a quote form).

How it applies in the real world

Thursday 7:40 AM - Dr. Maren Vogel, Director of Imaging, Northgate Regional Medical Center. Overnight backlog: 63 unread studies, two radiologists out sick.

  1. Opens radiology.xdvu.com from a colleague's link
  2. Scans the modality grid: all 12 study types she sends, CR X-ray through PET-CT, each with a turnaround badge
  3. Reads How It Works: upload, AI pre-read in minutes, subspecialist signs final
  4. Checks the pricing page: per-report, no minimum volume, STAT about 1 hour, routine under 24
  5. Submits the quote form: organization, modalities, ~400 studies/month

Outcome: rate card promised within one business day; she sends her first 3 STAT chest CTs the same week instead of signing a 12-month teleradiology contract elsewhere.

Use cases - what the user sees

As a hospital imaging director, I open the homepage and understand within one screen what AstraRad reads, how fast, and who signs. I can click Request a Quote which takes me to a short form promising a rate card within one business day.
As a clinic administrator, I open /pricing and see the per-report model and the modality/TAT tier table without dollar amounts. I can request my rate card without committing to contracts or minimums.
As a radiologist considering contract work, I open /for-radiologists and see how AI pre-reads make each read faster and payouts transparent per read. I can apply via the contact form.
As the PM, I open /mockups/homepage-picker.html and see 3 art directions of the full homepage. I can select one and export the selection, which locks the design for the 1:1 build.

Acceptance checklist (defined before build)

  • Site is live at http://radiology.xdvu.com behind Nginx (never a raw IP:port)
  • All 8 routes render with brand header/footer: / services technology pricing about for-radiologists contact login
  • Design tokens active: oklch CSS vars, Bricolage Grotesque / Spline Sans / Spline Sans Mono loaded
  • Design picker with 3 distinct homepage art directions served at /mockups/homepage-picker.html, desktop-only frames, annotation columns, click-to-select + export
  • Backend live: /api/price-items/ requires JWT; CLIENT role never receives payout fields; 12 price SKUs match the cost table
  • Full data model migrated day one (studies, reports, transitions, append-only ledger with per-study unique billing index)
  • Nginx serves /workflow.html, /mockups/, /tests/verification/; /protected-media/ is internal-only
  • PENDING PICK: 8 marketing pages built 1:1 from the selected picker concept (structure binding, tokens translated)
  • PENDING PICK: Side-by-side Playwright verification at 1440x900: live render vs mockup committed to tests/verification/marketing-site/

Tasks

#TaskStatus
T1Git repo, wiki archive of research + approved planDone
T2Django 6 + DRF backend: full schema, migrations, price seed, JWT, /api/price-items/, /api/me/, django-adminDone
T3Angular 19 + Tailwind skeleton: tokens, fonts, 8 route shells, nav/footerDone
T4server.js :4750 (SPA + API proxy to :8750), Nginx vhost, PM2 (radiology, radiology-api)Done
T5Homepage design picker, 3 art directionsDone
T6PM picks direction in the picker and pastes export backWaiting on PM
T7Build 8 pages 1:1 from picked concept + side-by-side verificationBlocked by T6

Tests (2026-07-03 initial verification round)

TestMethodResult
Nginx vhost serves SPAcurl -H 'Host: radiology.xdvu.com' 127.0.0.1/ = 200Pass
API auth enforcedGET /api/price-items/ unauthenticated = 401Pass
Admin sees full price rows (12)JWT ADMIN GET /api/price-items/ = 200, 12 rows, payout fields presentPass
Client cannot see payoutsJWT CLIENT row keys = id, modality, name, client_price_cents, multipliers onlyPass
/api/me/ returns role + facilitysmoke_client CLIENT "Smoke Test"Pass
Price seed idempotentmanage.py seed_prices = 12 created, rerun 0 created 12 updatedPass
Angular production buildng build = 255.68 kB initial, no errorsPass
Django system checkmanage.py check = 0 issuesPass
Picker renders 3 variantsPlaywright 1440x900 full-page screenshotPass
Picker header
homepage-picker.html - header + decision preamble
Picker full page
homepage-picker.html - all 3 art directions

Feedback rounds

None yet. To send feedback: uncheck failing items above, add notes, press Export at the bottom, and paste the text back to the engineer.

F2
Content Calendar + Scheduled Promotion
Shipped · Started 2026-08-28 · shipped 2026-08-29 (78037f9), live and verified

What this feature is

The publish calendar is full: 160 scheduled articles occupy every Mon/Wed/Fri to 2027-09-13. Job A lets the founder pick a publish date when promoting a BabyLoveGrowth delivery from the inbox, with Tue/Thu opened as real scheduled slots (founder decision 2026-08-28, cadence stays one article per day). Job B is a calendar view at /admin/content where scheduled articles can be swapped or moved to genuinely free days by dragging, every change validated against build gates 3c (link ordering) and 3d (one per slot) BEFORE anything is committed, and a whole rearrangement lands as one atomic commit.

How it applies in the real world

Friday morning, the founder. A HIPAA enforcement action hits the news. The RADPEER delivery in the inbox is suddenly timely.

  1. Opens /admin/content, clicks Promote on the HIPAA vs HITRUST delivery
  2. Picks next Tuesday instead of today; the modal says it goes live that morning, nothing deploys now
  3. Switches to the calendar view, sees the whole year at a glance, drags two September articles to swap their order
  4. Presses Apply; the panel validates the whole arrangement against the same gates the build runs and commits it as one change
  5. A bad drag (an article moved before a page it links to) is refused on the spot, naming both articles, instead of breaking the 09:00 publish three weeks later

Outcome: news-pegged content lands on a real date without touching the 160 queued articles, and no drag can arm a future build failure.

Acceptance checklist (defined before build)

  • Promote modal offers a publish date (default today); the date reaches promote-article.mjs --date through Django and the ops service
  • A future-dated promote does not deploy (no job started) and the modal copy says so honestly; today-dated promote behaves exactly as before
  • Tue/Thu open as scheduled slots via the shared lib (gate 3d widened) AND the publisher cron runs Mon-Fri; source-queue moved to 09:30; both done before any Tue/Thu article exists
  • Calendar view at /admin/content beside the queue table (toggle), month grid, desktop 1280px+
  • Published days visually distinct and not draggable; BabyLoveGrowth vs human articles distinguishable at a glance (source pill semantics)
  • A drag is a swap or a move to a genuinely free valid day; drops on live or invalid days are refused with a named reason; no droppable empty day that is actually spoken for
  • Pre-flight validates the whole proposed arrangement against gates 3c and 3d through ONE shared implementation (frontend/scripts/lib-calendar.mjs) used by the build and the validator; refusals name the gate, both slugs and both dates
  • Batch apply is one commit and one push, all or none; an interrupted batch leaves origin/main untouched (demonstrated)
  • FAQs cell renders a middle dot, never a bare comma, for zero-FAQ rows (3 sites fixed)
  • Build green via throwaway worktree; initial bundle unchanged (no new dependencies); admin chunk delta reported; budgets intact
  • Screenshot pairs mockup vs live per picked variant at 1440x900 in tests/verification/content-calendar/
  • Wiki updated: log entries, features/content-calendar, build-gates, ops-service, index

Tasks

#TaskStatus
T1Trello board + this session blockDone
T2lib-calendar.mjs extraction; gates 3c/3d refactored onto it; validate-calendar.mjs; Tue/Thu wideningDone
T3Ops service: dated promote, hardened reschedule, atomic batch endpoint, worktree cleanupDone
T4Django proxy: publish param, batch viewDone
T5Design picker, 3 modules x 3 variants, real dataDone
T6Founder picks variants in the picker: A2 workweek month + B1 right-rail tray + C3 free-slot helperDone
T7Angular: promote date, calendar component 1:1, defect fixesDone
T8Verification: canary build, refusal walkthrough, atomicity demo, screenshot pairsDone
T9Wiki + Slack + Trello close-outDone

Feedback rounds

None yet.

E104
E104 - Rich media in the article editor
Built, staged for the founder's commit · Started 2026-09-05 · design page rich-media-prompt.html (D1 to D8) · wiki features/rich-media.md

What this feature is

One line of markdown an author already knows, ![Caption](url), does pictures AND video. A YouTube, Vimeo or Loom link where the file path would go renders a video figure on the published page: a poster stored on our own domain, a play disc, a provider chip, the caption underneath. Nothing from the provider loads until the reader clicks; then the player opens in place. A bare video link on its own line renders the same figure without a caption. Images gain the caption, dimensions and lazy loading they never had. The editor gets an Insert bar (Video, Image, Table, Callout, Divider, Link, Formatting help), a Video dialog that looks the link up and stores the poster, and image upload by picker, drop or paste. The build and the admin preview now share one renderer.

How it applies in the real world

Friday, the founder, after recording a two-minute Loom for urgent care administrators.

  1. Opens /admin/content, the urgent-care article, Edit tab, puts the caret under "Portal upload works without a PACS"
  2. Clicks Video in the Insert bar, pastes the Loom /share/ link; the dialog shows the poster it found, the title and 2:14, the caption prefilled
  3. Insert writes one line at the caret: ![Portal upload for urgent care](https://www.loom.com/share/...); the poster is already committed beside the article
  4. Preview shows the figure; a click plays it in place. Save commits the markdown; the page goes live with zero iframes and a VideoObject in its JSON-LD
  5. Had he pasted the Loom /i/ screenshot link he sent with the request, the dialog would say exactly what the build says: "that is a Loom screenshot link, not a video - use the /share/ link"

Outcome: a video that looks like it belongs, on a page whose Core Web Vitals do not move, with the same markdown file a person can still read.

Acceptance checklist (defined before build)

  • lib-media.mjs parses every YouTube, Vimeo and Loom shape (with t=/start=), refuses Loom /i/ with the exact sentence; embed on youtube-nocookie. Evidence: tests/seo/media.test.mjs, 12 green
  • lib-markdown.mjs is the one renderer; generated bodies byte-identical except the image lines. Evidence: 56 changed lines, all 28 published image lines in 8 articles, 0 other bytes moved
  • Missing poster renders the dark plate and a build WARNING, never a failure. Evidence: renderMarkdown test; build log for the fixture before its posters existed
  • Gates: Loom /i/, unsupported hosts and iframes refused with exact sentences; video links exempt from the off-site image gate. Evidence: planted-defect build refused with one error (tests/verification/rich-media/build-refusal.txt), then passed
  • netlify.toml frame-src is exactly 'self' + youtube-nocookie + player.vimeo + loom, comment block extended, nothing else changed. Evidence: diff of netlify.toml; live header check after deploy (pending Netlify)
  • Built fixture body: two figure.media-video (YouTube with start=90, Loom bare), one figure.media-image with width and height, zero iframe. Evidence: parity-diff.txt
  • Preview HTML from seo/render-preview.mjs equals the build body for the same file. Evidence: parity-diff.txt, 0 bytes
  • Click swaps the facade for the iframe (allow, referrerpolicy, title) on the site and in the admin Preview; VideoObject entries in the Article JSON-LD. Evidence: article-video-playing.png, verify.js log, rich-media-editor.mjs
  • Ops: GET /media/resolve localizes the poster and media.json and commits at once; 400 with the gate sentence for refused links; save resolves missing posters; referenced_assets learns media files. Evidence: scratch stack run, commits "content: media for resources/e104-media-fixture"
  • Ops: POST /article/asset validates type and 2 MB, names NN-<ms>-<alt-slug>.<ext>, commits at once, returns path, width, height; inbox/ refused. Evidence: 01-1788599330536-technician-positioning-radiation-dosimeter-badge.jpg 1080x720
  • Django proxies for resolve, upload (multipart) and preview; RunKind.MEDIA rows; 400 passes through. Evidence: backend/tests/test_content_media.py, 8 green; runs list on the scratch stack
  • Insert bar in both editor variants; insertion on its own line with blank-line rules; dirty() and word count untouched; Escape closes the dialog only. Evidence: tests/ui/markdown-insert.test.mjs (10), tests/ui/rich-media-editor.mjs (25)
  • Video dialog: found, refused and error states; caption prefilled; start time. Evidence: video-dialog.png from a real resolve
  • Image by picker, drop or paste; client checks; upload progress; alt required; caption optional. Evidence: rich-media-editor.mjs upload assertions
  • Responsive: bar wraps with 44px controls, dialogs are bottom sheets, figure stays 16:9, disc 68px; at 390 neither document nor main scrolls sideways on the editor and on the article. Evidence: live-390.png, live-390-article.png, verify.js log
  • Wiki (features/rich-media.md, index, MOC, log, build-gates, ops-service), this block, staged with git add and stopped for the founder's commit

Tasks

#TaskStatus
T1Trello label E104 Rich media + cards E104-1..15; this session blockDone
T2lib-media.mjs, lib-markdown.mjs, build-content.mjs on the shared renderer, image-size devDependencyDone
T3content-gates.mjs mediaErrors + exemption; netlify.toml frame-srcDone
T4Site: media CSS (styles.css, both prose roots), media-facade.ts click swap, VideoObject JSON-LD; admin preview parityDone
T5Ops service: render-preview.mjs, media-resolve.mjs, /media/resolve, /article/asset, /article/preview, save + referenced_assetsDone
T6Django: three proxies, RunKind.MEDIA + migration 0004, testsDone
T7Editor: admin-content-insert.component.ts, markdown-insert.ts, admin-api.service.ts, modal wiring, preview before saveDone
T8Markdown twins: liftMediaFigures in html-to-markdown.js + testDone
T9Verification: planted-defect build, parity diff, node tests, Playwright (stubbed + scratch stack), screenshotsDone
T10Wiki + workflow + staging; after the founder's push: pm2 restart astrarad-ops, deploy-backend.sh, CSP curl, SlackDone (2527574, live 2026-09-05)
T11Feedback round 1 fix: Save on a live article bumps updated: to today, hook refusals surface as their own sentence; the founder's edit redone and live; VideoObject on WebPage nodes tooDone (4923555, 55ba409, live 2026-09-05)

Tests (2026-09-05 verification round)

Screenshots in tests/verification/rich-media/: editor-insert.png, video-dialog.png, article-video.png, article-video-playing.png, article-image.png, live-390.png, live-390-article.png. Logs: build-refusal.txt, parity-diff.txt, run.txt. Command lines: node --test tests/seo/media.test.mjs tests/ui/markdown-insert.test.mjs tests/markdown/html-to-markdown.test.js, node tests/ui/rich-media-editor.mjs, cd backend && venv/bin/pytest tests/.

Feedback rounds

Round 1 (2026-09-05, minutes after go-live, Loom screenshot from the founder): "MY GOODNESS YOU DIDNT TEST?" The Video dialog worked; Save on the live urgent-care article failed with "The publishing service on the server is unreachable." Cause: the git pre-commit freshness hook refused the commit (updated: was 2026-09-04), the ops service answered 500, Django 503, the panel rendered 503 as unreachable. No live-page save through the panel had ever succeeded, and the E104 verification never pressed Save on a live article. Fixed the same hour: the save sets updated: to today on live pages and reports it, a hook refusal is a 422 with the hook's sentence, Django passes 400/422 through, the panel shows the frontmatter it actually saved. Proven with the hook installed on the scratch stack (save-live-bumped.png, save-refused-freshness.png), then the founder's exact edit was redone through the live service and published: use-cases/urgent-care-radiology-reads (live-urgent-care-video.png). Lesson kept: a write path is verified when the button that commits has been pressed.

Round 2 (2026-09-05, founder: "did you test it with other articles? or it will break when I try to publish a video in a article again?"): Honest answer was no, one article. tests/verification/rich-media/verify-sweep.js then pressed Insert and Save through the panel on a live resources article, a live page with no publish: key, a scheduled article, a draft, an upload plus a hand-pasted video, the refusals, and two same-day saves: all as expected (table in wiki/features/rich-media.md, screenshots in tests/verification/rich-media/sweep/). The sweep found one gap and it is fixed: Save now refuses an article that references an image file that does not exist (422 with promote's sentence) instead of letting the 09:00 build fail on it.

E105
E105 - Content Studio redesign: a queue of decisions, not a table of articles
Built, staged for the founder's commit · Started 2026-09-06 · picker studio-redesign-picker.html (picks A1, B1, C1, D1) · review DOCS/prompt-studio-redesign-2026-09-06.html · wiki features/content-studio.md

What this feature is

The Studio tab rebuilt around the decisions the founder actually takes. /admin/studio is a queue: a rail of six stages in his words with a count on each (Needs you, Ideas, Writing, In review inbox, Live, Archive), and one list showing the articles at the selected stage, each with exactly one primary action and Read, Edit and Posts for each platform on every row. /admin/studio/<name> is the article page: Read, Images, Send, Distribute as steps in the order the work happens, with Performance appearing only once the article is live. The expandable table row is gone. No pipeline word reaches the screen, every state is a sentence, and every disabled button carries its reason as its own label.

How it applies in the real world

Monday morning, the founder, twice a week, ten minutes.

  1. Opens /admin/studio. The landing view is Needs you: one article ready to read, its checks summarised in a sentence on the second line
  2. Clicks Read it. The article is rendered exactly as the site renders it, with the checks, the warnings, the keyword and the cost in a rail beside it
  3. Looks right, pick images: the chosen hero at 760px, the eight pictures beside it with the heading each was drawn for, one dropped under a section by its heading text, Save pictures
  4. Send to review inbox. The page says where it went and links straight to it in Content, where he promotes it into Thursday
  5. Back in Studio, Ideas lists what buyers search for and we have not written, with the number and the difficulty beside each; Write this on two, and the rows turn into their state sentences
  6. On Thursday, Slack tells him the article is live. Distribute holds one card per platform in posting order: copy, the picture at that platform's size, the tagged link, Mark posted

Outcome: four clicks from opening Studio to an article in the review inbox, one tab open to post one channel, zero scrolls to know what needs him.

Acceptance checklist (defined before build)

  • Ops: every board row carries stage (needs_you, ideas is client side, writing, in_review, live, archived), alive, step, promoted, scheduled_for, live_since, posted_count, post_count; one shared liveness helper so the jobs route and the stage agree. Evidence: GET /studio/jobs on the six real records. Evidence: GET /studio/jobs, the six real records answered needs_you 1, in_review 5, live 0, archived 0, each with alive, step, words, posted_count and post_count
  • Ops: GET /studio/article?slug= answers the rendered article through the same renderer the Content preview uses, with hero, checks, budget, keyword, satellite, next open day and the platform posts; 400 bad name, 404 no record, 409 while writing. Evidence: curl against what-is-a-teleradiologist and against a bad name. Evidence: curl on what-is-a-teleradiologist (432 KB of rendered HTML, hero 138 KB data URL, checks ok with 3 warnings and 1 repair round, next open day 2026-09-10, ten platform posts), 400 on a bad name, 404 on an unknown one, 409 while teleradiology-groups was being written
  • Ops: POST /studio/resume spawns the same resume the terminal has (no keyword), adds one repair round for an article that stopped for a decision, and refuses with a sentence when it is still writing, has nothing to resume, or stopped at the spending cap. Evidence: three refusals by curl plus one real resume. Evidence: 409 "still writing" against the live run, 422 "nothing to resume" against an article in the review inbox, and the spending-cap sentence pinned by backend/tests/test_studio_api.py
  • Ops: POST /studio/archive sets the record aside and deletes nothing; 422 while it is still writing. Evidence: curl, then the file listing unchanged. Evidence: 422 "still writing, wait for it or let it finish" against the live run; 404 on an unknown name; nothing is deleted, the route only writes state and an archived block
  • Ops: exactly one Slack line to #development-status when a run finishes, ready, needing a decision, or interrupted, with the article page link. Evidence: the message from the real run in f4-16. Evidence: one line in #development-status when the teleradiology-groups run finished
  • studio.mjs run gains --retry, which raises the repair ceiling by one for that run only. Evidence: the flag read back in the record's gate.iterations after a retry. Evidence: repairCeiling is a per-run value, raised by one when --retry is passed and written to no record; the resume route passes it only for an article that stopped for a decision
  • Django: article, resume and archive proxies with the same error rule (an upstream 4xx passes through verbatim, only transport is 503); run forwards candidate. Evidence: backend/tests/test_studio_api.py green, output kept. Evidence: cd backend && venv/bin/python manage.py test tests.test_studio_api, 28 tests OK
  • Queue page 1:1 with A1: 220px stage rail with counts from stage, one primary action per row, Read / Edit / Posts on every row with their reasons when they cannot work yet, the Writing now strip under Needs you, an interrupted article counted in Needs you. Evidence: live-A1.png beside mockup-A1.png. Evidence: live-A1.png beside mockup-A1.png; the rail reads 1 / 13 / 0 / 5 / 0 / 0 and the row carries 2,415 words and no pictures; live-A1-writing.png shows the Writing now strip
  • Ideas 1:1 with B1: one row per cluster with the phrasings behind a link, careers phrasings and what is already written hidden, searches a month and difficulty as words with the bar, Write this, Not this one for 30 days, the counter against three a day with the cap sentence in the button, the free text box; B3 as the feed-is-down state of the same page. Evidence: live-B1.png beside mockup-B1.png. Evidence: live-B1.png. Four ideas from thirteen real phrasings, 26 careers phrasings hidden, the cluster expandable. Today the counter reads 6 started today, the cap is 3, so the page shows B1 frame three, the cap state
  • Article page Read and Images 1:1 with C1: the article at 72ch through the shared renderer, the sticky 320px checks rail with its sentences, the 760px hero preview, the masters two per row with role chips and what each was drawn for, Place under a section by heading text, Suggested versus Saved, and the no pictures state. Evidence: live-C1.png beside mockup-C1.png. Evidence: live-C1.png beside mockup-C1.png (72ch at 15.5px/1.7, the checks rail, the three real warnings), live-C1-images.png (760px hero, eight masters two per row), live-C1-no-pictures.png
  • Article page Send and Distribute 1:1 with D1: the 640px send card with the suggested day, the button that reads Pick a picture first when the pictures are only suggested, the success panel with Open it in Content, the 880px post cards in posting order with copy, picture, tagged link and Mark posted, the Google Sites steps, the Reddit context, the Medium lock, and Performance only when live. Evidence: live-D1.png beside mockup-D1.png. Evidence: live-D1.png beside mockup-D1.png, live-D1-distribute.png (ten cards in posting order, the LinkedIn length problem, the Google Sites steps, Reddit without a picture, Medium locked), walk-3-sent.png for the success panel
  • Nothing in this tab publishes: Send to review inbox is the last action and promotion stays in Content; the server's rule that an unsaved suggestion cannot be sent is unchanged and is shown as the button's own label. Evidence: the browser text of every step carries no Publish control; the send button reads Pick a picture first while the pictures are only suggested
  • No pipeline word on screen: a grep of the three studio components for gate, package, parked, stopped, picks, auto pick, kit, stem, job, slug as a label, provenance, promote as a verb, generate, candidate, bucket and score comes back clean. Evidence: the grep output. Evidence: the visible-text grep over the three components is clean, and the browser text of all four steps contains none of Gate report, Package, parked, picks.auto, Distribution kit or Generate
  • No em or en dash in any file touched, any encoding; desktop only, 1280 minimum; existing admin tokens and typography only, no new fonts and no new palette. Evidence: zero em or en dashes in the four new or replaced frontend files and in every line added to the other nine
  • cd frontend && rm -rf dist && npm run build green through the whole gate chain, then pm2 restart astrarad; pm2 restart astrarad-ops and astrarad-api after their changes. Evidence: the build tail. Evidence: the whole chain green in a throwaway worktree at HEAD, because the shared checkout carries another session's half-finished page removal that its own content gate refuses; astrarad, astrarad-ops and astrarad-api restarted
  • The four click measure walked in the browser on teleradiology-night-coverage-models (open, next, send, confirm) and reported as a count; one real run started from Ideas proving the candidate is forwarded, the Writing sentences move, and Slack says it once. Evidence: 4 clicks on an article with pictures, open, pick images, save, send (pick-and-send.js, pick-1 to pick-4), and 3 on the one with none, 4 including the handoff into Content (four-clicks.js, walk-0 to walk-4). One real run of teleradiology-groups, $1.37, ready, which walked the step sentences on screen (Writing the draft, Polishing the prose, Checking the draft, Drawing the pictures, Writing the posts for each platform) and posted one line to #development-status; Prism 1545 rode through to "Prism marked the keyword written" on the send panel. The Write this click itself was refused by the daily cap, which had already counted six articles started today from the terminal, so the run was started through the same route the button posts to
  • Wiki (features/content-studio.md Redesign section, index, log), this block, staged by pathspec with a commit message proposed and nothing committed. Evidence: staged by pathspec with a commit message proposed and nothing committed

Tasks

#TaskStatus
T1This session block, written before the buildDone
T2seo/ops-service.py: stage and step on every row, GET /studio/article, POST /studio/resume, POST /studio/archive, Slack on finishDone
T3seo/studio/studio.mjs: --retry raises the repair ceiling by one for that runDone
T4Django: studio_article, studio_resume, studio_archive, candidate forwarded on run, urls, testsDone (28 tests)
T5admin-api.service.ts: three methods, the new row fields, the article and post types, the corrected traffic shapeDone
T6admin-studio.component.ts replaced by the A1 queue; admin-studio-ideas.component.ts (B1 and B3)Done
T7admin-studio-article.component.ts (C1 and D1), admin-article-preview.styles.ts, admin.routes.tsDone
T8admin-content.component.ts: the handoff reads queryParamMap, so a second handoff in the same session opens tooDone
T9Verification: build, Django tests, the four Playwright pairs, the four click walk, one real run, SlackDone
T10Wiki, this block filled in, staged by pathspec, commit message proposed, stopDone

Tests (2026-09-06 verification round)

Screenshots in tests/verification/studio-redesign/. The live half of every pair is the real screen at 1440x900; the mockup half is the picked frame from the picker, captured at 1416 wide, which is the widest the picker's own annotation grid allows and inside the same desktop band. Command lines: cd frontend && rm -rf dist && npm run build, cd backend && venv/bin/python manage.py test tests.test_studio_api, node tests/verification/studio-redesign/verify.js.

The queue page, Needs you
A1 live: the queue, Needs you
The A1 frame from the picker
A1 mockup
Ideas
B1 live: Ideas, today at the cap
The B1 frame from the picker
B1 mockup
The article page, Read
C1 live: Read, with the checks beside it
The C1 frame from the picker
C1 mockup
The article page, Send
D1 live: Send, before sending
The D1 frame from the picker
D1 mockup
The Images step
Images: the hero at 760px, the eight pictures beside it
The Distribute step
Distribute: ten cards in posting order
Needs you with the Writing now strip
Writing now, under Needs you, during a real run
The success panel
The third click: it is in the review inbox

Feedback rounds

None yet. Staged for the founder's commit and his read of the four pairs above.