Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion documentation/architecture/ROUTE_MAP.md

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions documentation/architecture/decisions/D599.md
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,8 @@ The owner's new export replaces the old one in place. Its route is live, as
views or stores answers yet. #1286 (S10, D603, migration 408) builds both
stores, and Session 1 wires them here afterwards. Until then the contract
test keeps them banned.
**Since D621 (#1280 step 2),** both are built: popular cards on the home,
the yes/no on each guide, and the answers for an admin.
- **The per-guide surface tag, "Where this lives", "Open the surface" and
"Applies to" stay out.** No guide records its surface or its licences. Adding
them is #1280 step 3, content authoring.
Expand Down
2 changes: 2 additions & 0 deletions documentation/architecture/decisions/D603.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ member per guide, for "Did this answer it?".** Slot S10, issue #1286, step 2 of
#1280, 2026-10-06. Migration 408. This PR builds the stores, the routes and
the client. Session 1 wires the page under #1280; until then
`frontend/test/help_center_contract.test.mjs` keeps both off it.
**D621 (#1280 step 2) wires the page**, and shows the admin read on the Help
Center home.

### The anchor

Expand Down
147 changes: 147 additions & 0 deletions documentation/architecture/decisions/D621.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
## D621 — Help Center: "Popular this week" and "Did this answer it?", wired to their stores, with the answers shown to an admin

**Issue #1280, step 2, slot S01, 2026-10-07.** Frontend only: no route, no
migration. D603 (#1286, migration 408) built the stores, the routes and the
client. This wires the page to them and lifts the two bans the contract test
held until then.

### What was true on main

- **The canvas** (`design/canvases/integrated/Help Center.dc.html`) draws:
- "Popular this week" on H1, the home: four ranked cards, "By views, last 7
days";
- "Did this answer it?" on H2, each guide: Yes and No, "A no opens one
field asking what was missing".
- **D599** built H1 and H2 without them, for want of a store, and
`help_center_contract.test.mjs` banned both phrases from the layout.
- **D603** built the stores and the routes:
- guide views by day, one counted per member per guide per day;
- one yes/no answer per member per guide, the last one wins;
- an admin read of every answer, with no member identity.

Nothing on the page called them, and no screen showed the admin read.

### The change (`pages/docs/HelpSignals.jsx`, mounted by `DocsLayout.jsx`)

**Popular this week, on the home**
- It reads `GET /api/help/popular?days=7&limit=10` and draws the first four
guides this reader can open, in the route's order (most views, then the
anchor).
- **A guide the reader cannot open, or one the manifest no longer has, is
left out,** never shown as a dead card. The page asks for the route's
maximum, 10, so a dropped guide leaves room for the next.
- **Each card** has a rank (the first violet, as drawn), the guide's title,
and "1,240 views · Capital", with the area's name. A view is one member
reading a guide on one day (D603).
- **It sits under the areas,** and steps aside while a search is shown.
- **States:**
- loading draws four skeleton cards and no figure;
- a failed read says "The most-read guides could not be read. This is not
a sign nobody reads them.", with Try again;
- a week with no views for the reader's guides says so.

**Did this answer it?, on each guide**
- **Opening a guide records the view** (`POST .../view`). The Worker counts
one per member per guide per day.
- **The card reads the reader's own answer:**
- with none, it offers Yes and No;
- with one, it says "You said this answered it." or "You said this did not
answer it.", quotes what was missing, and offers "Change your answer".
- **No opens one field,** "What was missing?":
- it is optional;
- it is capped at the Worker's 500 characters, with a counter;
- Send saves the no, with the text.
- **Yes saves at once.**
- **States:**
- while the answer loads, there are no buttons;
- an answer that could not be read still lets the reader answer, and says
a new answer replaces it;
- a failed save says "Your answer was not saved. Try again.".
- **The card sits before Related,** as drawn.

**The answers, for an admin, on the home**
- **One card,** "Did this answer it? · every member", under the tickets card,
for an admin only: `GET /api/admin/help/feedback` is `requireAdmin`.
- **For each guide,** the yes and no counts, most "no" first (the route's
order), linked to the guide. A guide the manifest no longer has shows its
anchor.
- **Then each "What was missing",** newest first, with its guide and date.
- **The first five of each show,** then Show all.
- **The meta line totals the answers** and says "no names": the route sends
none.

### Where the build departs from the canvas, and why

- **The note says who reads the answers.** The canvas says a no "goes to
whoever wrote the guide". No guide records its author, so that would be a
promise nobody keeps. The note says "Axal's admins read the answers,
without your name", and the admin card makes that true.
- **The admin card is not drawn.** Without it, no screen would show a single
answer, and "Did this answer it?" would collect text nobody reads. It is
one card on the home the admin already uses, built from the canvas's own
card and list styles.
- **States the canvas does not draw:**
- loading;
- a failed read;
- an empty week;
- a changed answer;
- an unreadable earlier answer;
- a failed save.

### Still out (#1280 step 3)

Two canvas elements still have no store, and the contract test still bans
them:
- the per-guide surface line and "Open the surface", since no guide records
its surface;
- "Applies to", since no guide records the licences it applies to.

### Tests

**`frontend/test/help_signals_d621.test.mjs` (12):**
- **The constants against the Worker's:** the route's maximum limit and
window, and `MISSING_MAX`.
- **The popular cards:**
- the route's order;
- guides the reader cannot open left out;
- zero views dropped;
- four at most;
- no list read as a failed read.
- **The reader's answer:** none; yes; no, with and without text; and
unreadable.
- **The admin view:** names, a removed guide, the totals, and that nothing
about a member passes through.
- **Every rendered state** of the three parts.
- **The wiring:**
- where each part mounts, and that the admin card shows only for an admin;
- each store read through the module file;
- each failure kept apart from an empty answer.

**`frontend/test/help_center_contract.test.mjs`:** the two bans lifted, on
purpose; its header now names only the two that remain.

**Mutation checks: 24 of 24 caught.** Each undid one behaviour, and every file
was restored and checked by sha256:
- **the cards:**
- a guide the reader cannot open, or with no views, shown;
- no cap at four, or asking the route for only four;
- a failed read as an empty week;
- "1 views";
- two violet ranks;
- **the answer:**
- the field's cap off the Worker's, or no cap on the field;
- a reply without the field read as no answer;
- a yes keeping stray text;
- the note promising the guide's author;
- a no saved at once with no field;
- an unreadable answer blocking a new one;
- a failed save left silent;
- the view not recorded;
- **the admin card:**
- a member field passed through;
- every row at once;
- a failed read silent, or read as none;
- **the layout:**
- popular under search results;
- the admin card for everyone;
- no answer card on a guide.
17 changes: 12 additions & 5 deletions frontend/src/pages/docs/DocsLayout.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import BountyBadge from '../../components/tickets/BountyBadge';
import ReviewBadge from '../../components/tickets/ReviewBadge';
import { statusChip } from '../../lib/ticketScreens';
import { parseGuidePath, guideHref, hashTarget, readingMinutes, openTicketsByActivity } from './helpCenter';
import { GuideAnswersAdmin, GuideFeedback, PopularGuides } from './HelpSignals';

// ---------------------------------------------------------------------------
// D599 (#1280) — the Help Center, built to its canvas.
Expand All @@ -37,11 +38,13 @@ import { parseGuidePath, guideHref, hashTarget, readingMinutes, openTicketsByAct
// notifications, the advisor's tools and bookmarks, so a hash on `/help` now
// opens that guide's page (see `DocsLayout` below).
//
// Four canvas elements stay out, each for want of a store, and
// `help_center_contract.test.mjs` pins their absence: the view-ranked list
// (nothing counts views), the yes/no answer (nothing stores it), the per-guide
// surface line and jump (no guide records its surface), and the licence line
// (no guide records the licences it applies to). #1280 steps 2 and 3 add them.
// D621 (#1280 step 2) wires two canvas elements to D603's stores, in
// `HelpSignals.jsx`: "Popular this week" on the home, and "Did this answer
// it?" on each guide, with the answers shown to an admin on the home. Two
// stay out, for want of a store, and `help_center_contract.test.mjs` pins
// their absence: the per-guide surface line and jump (no guide records its
// surface), and the licence line (no guide records the licences it applies
// to). #1280 step 3 adds them.
// ---------------------------------------------------------------------------

// Wrap the pure-JS split helper into a JSX-friendly highlighter. Kept
Expand Down Expand Up @@ -583,6 +586,8 @@ function HelpArticle({ sections, area, guide }) {
)}
</div>

<GuideFeedback anchor={`${section.id}/${sub.id}`} />

{related.length > 0 && (
<section id="related" className="mt-7 scroll-mt-6">
<h2 className={LABEL}>Related</h2>
Expand Down Expand Up @@ -678,7 +683,9 @@ function HelpHome({ visibleSections, role, user, query, setQuery, searchInputRef
) : (
<BrowseByTask sections={visibleSections} />
)}
{!trimmedQuery && <PopularGuides sections={visibleSections} />}
<YourTicketsCard isAdmin={role === 'admin'} />
{role === 'admin' && <GuideAnswersAdmin sections={visibleSections} />}
<StillStuck user={user} onOpenChat={onOpenChat} />
</div>
</>
Expand Down
Loading
Loading