Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NHyYupFdBiR4VfoUM7NjSM
48 lines
3.0 KiB
Markdown
48 lines
3.0 KiB
Markdown
# Step 2 — Full issue views, rating widget, POST /rate
|
|
|
|
Read `00-shared.md`, then the plan `docs/plans/2026-09-03-web-dashboard.md`
|
|
(§3, §4.3–§4.4, §5.4, §6.3–§6.4, §8 including §8.1, §16, §17), then
|
|
`handoff-step1.md` for what the foundation actually shipped. This is plan §19
|
|
item 2. Build on the step 1 code as it exists; do not restructure it.
|
|
|
|
## Deliverables
|
|
|
|
1. `issue_full.html` (§8): masthead + dateline, The Brief (`front_page_html`
|
|
`|safe`), download buttons (from `IssueView.downloads`), the per-section
|
|
index like the EPUB's In-this-issue page (title → article page,
|
|
`source · N min read`, summary, Why it's here), the rating widget for admins,
|
|
links to World Briefing / Behind the paper (only when present), colophon
|
|
facts footer. `/` and `/issues/{date}` render this for any signed-in viewer.
|
|
2. `article.html` at `/issues/{date}/articles/{article_id}` (§8): header
|
|
(title → source, byline, meta line, Why, social line via
|
|
`chapters::social_line`, summary), body from `ammonia::clean` with the same
|
|
configuration the EPUB uses (find it in `src/html.rs`/`src/epub`; do not run
|
|
`to_xhtml`), `<img>` keep remote `src` and get `loading="lazy"
|
|
referrerpolicy="no-referrer"`, the discussion via `comments::render_xhtml`
|
|
when present, prev/next in issue order, "Read online ↗", rating widget for
|
|
admins. 404 (site error page) when the article is not in that issue.
|
|
3. `world.html` (`/issues/{date}/world`, `world::render_xhtml`, 404 when
|
|
absent) and `behind.html` (`/issues/{date}/behind`, the same lines as the
|
|
EPUB chapter via `chapters::behind_*_line`; near misses link to
|
|
`/dashboard/articles/{id}` for admins).
|
|
4. These three routes sit under the `login_required!` layer (§3).
|
|
5. The HMAC confirmation page (`server::confirmation_page`) gains the
|
|
"Open this issue on the site" link (§8).
|
|
6. Rating widget partial `_rating_widget.html` + `src/web/rate.rs` (§8.1):
|
|
`POST /rate` under the admin layer and the origin check; form and JSON
|
|
variants; `RatingEvent { source: "dashboard", user_id: Some(viewer.id) }`;
|
|
`cleared` writes value 0.0 with label `cleared`; `issue_date` optional with
|
|
the dashboard fallback (`db::latest_issue_date_for_article`); JSON →
|
|
`200 {"article_id","label","event_id"}`, form → flash + 303 to a validated
|
|
same-site `next`. Check how the CLI (`main.rs` `cmd_ratings`) writes the
|
|
`cleared` event and match it exactly.
|
|
7. `app.js`: intercept `.rating` forms, `fetch` with
|
|
`credentials: "same-origin"` and `Accept: application/json`, update the
|
|
active button; keep the no-JS form path working.
|
|
8. Tests (§17 "Full rendering", "Rating"): signed-in `/issues/{date}` contains
|
|
Brief, summaries and why; article page contains body and discussion; the
|
|
fallback loader (no `issue_json`) renders without world/discussion links;
|
|
downloads listed only for files that exist; `POST /rate` as admin appends a
|
|
`dashboard` event with `user_id`, as user → 403, anonymous → redirect/401,
|
|
JSON and form variants, `next` validated, `cleared` value 0.
|