@atelic-action/artifact 4.1.0 → 4.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -2,12 +2,12 @@
2
2
 
3
3
  The documents the practice hands or sends a business, as a published package and as the site a business's folder is cut from. This repo is two things at once:
4
4
 
5
- - **The package**, `@atelic-action/artifact` on npm: the composed documents (the writeup, the read, the monthly page, the note, and the sealed document), each with its prop types and its default labels, and the stylesheet they wear. It ships `src/documents/` and nothing else, as source, the way `@atelic-action/ui` does, and depends on that package for every piece a document is built from.
5
+ - **The package**, `@atelic-action/artifact` on npm: the composed documents (the writeup, the read, the monthly page, the note, the first call sheet, and the sealed document), each with its prop types and its default labels, and the stylesheet they wear. It ships `src/documents/` and nothing else, as source, the way `@atelic-action/ui` does, and depends on that package for every piece a document is built from.
6
6
  - **The seed**: the routes, the content stubs, the config, the stub files the host finds by path, and the tests a new folder of `atelic-action/artifacts` is cut from, and the template site that shows every document on fictional content.
7
7
 
8
8
  Three layers, each a version the next depends on (ATE-671): `@atelic-action/ui` holds the pieces, this package holds the documents built from them, and each business's folder holds only its content, routes, and config. A document fix reaches a folder by bumping this package; a piece fix by bumping ui here, then this package in the folder.
9
9
 
10
- Five artifacts live in the template site: the **writeup** at `/writeup`, the graded document an owner gets, a web page first with its printed copy derived from it; the **read** at `/the-read`, what a customer gets at the end of an engagement's first week, the baseline's grades explained by cause, with the plan in order between the gist and the cards; the **proposal** at `/proposal`, sealed into the page and opened by the reader's own email address; the **monthly page** at `/report`, what an Operate client gets each month on one letter sheet; and the **note** at `/notes`, the first touch for a business whose site publishes no email address, set as a short page. The bare host sends a reader to the writeup. Every artifact prerenders to static HTML, stays out of every index, and carries the practice's brand by default.
10
+ Six artifacts live in the template site: the **writeup** at `/writeup`, the graded document an owner gets, a web page first with its printed copy derived from it; the **read** at `/the-read`, what a customer gets at the end of an engagement's first week, the baseline's grades explained by cause, with the plan in order between the gist and the cards; the **proposal** at `/proposal`, sealed into the page and opened by the reader's own email address; the **monthly page** at `/report`, what an Operate client gets each month on one letter sheet; the **note** at `/notes`, the first touch for a business whose site publishes no email address, set as a short page; and the **first call sheet** at `/first-call`, the desk's page for the first call with a business that booked off a cold first touch, used to guide the call and not sent for now, written so it could be shared with them later. The bare host sends a reader to the writeup. Every artifact prerenders to static HTML, stays out of every index, and carries the practice's brand by default.
11
11
 
12
12
  The method behind them (the seven sections, the four grades from A to D, the rented line, what moved, and when an artifact is private) is the practice repo's `FOOTPRINT.md`. This repo builds to it and never restates it.
13
13
 
@@ -26,13 +26,17 @@ Every export of the package, all from the one entry, `@atelic-action/artifact`.
26
26
  | `ratingCell` | A star rating table cell for a writeup table, sorted by its figure | `/writeup` |
27
27
  | `WriteupBlock`, `WriteupTopic`, `WriteupPart`, `WriteupSheet`, `WriteupSummary`, `WriteupSummaryRow`, `WriteupMarkets`, `WriteupRented`, `WriteupMark`, `WriteupCell`, `WriteupTone`, and the rest of the `Writeup*` types | The shapes a writeup's content is written in | none |
28
28
  | `WriteupGrade`, `WriteupPerson`, `WriteupShot` | The grade letter, the sign off person, and the owner's screen, shared by every document, all three re exported from ui | none |
29
- | `Read`, `ReadProps` | The baseline's grades explained by cause: the gist table, each row a link to its card, with an optional `note` callout over it for what the read leaves out; each section on the writeup's card (`ReadSection` extends `WriteupTopic`: the trade name linked to its definition, the one liner, the grade, the why line as the summary, the writeup's blocks, and the sources at the foot) plus the read's own today and after (a two column table) and what we recommend (a checklist). The plan, numbered and in order, sits between the gist and the cards, so the whole program reads before any one card's detail. A beat with no data is left out | `/the-read` |
29
+ | `Read`, `ReadProps` | The baseline's grades explained by cause: the scorecard, a `ReportCard` with each row a link to its card, with an optional `note` callout over it for what the read leaves out; each section on the writeup's card (`ReadSection` extends `WriteupTopic`: the trade name linked to its definition, the one liner, the grade, the why line as the summary, the writeup's blocks, and the sources at the foot) plus the read's own today and after (a two column table) and what we recommend (a checklist). The plan, numbered and in order, sits between the gist and the cards, so the whole program reads before any one card's detail. A beat with no data is left out | `/the-read` |
30
30
  | `readLabels`, `ReadLabels` | The fixed words the read sets (Prepared for, the scorecard's screen reader words, Today, After, Recommendations, the plan's heading), each overridable through `labels` | `/the-read` |
31
31
  | `ReadTier`, `ReadSection`, `ReadChange`, `ReadStep`, `ReadPlan`, `ReadClose`, `ReadLink`, `ReadParagraph` | The shapes a read's content is written in. `ReadSection` is a `WriteupTopic` plus `gist`, `changes`, and `recommend`; its `why` and a finding's `body` take a paragraph (a string, or runs of text and links) as well as a node, so a routine can still write a read as JSON | none |
32
32
  | `Report`, `ReportProps` | The monthly page: the four questions, the seven grades now and before, the sign off, on one letter sheet | `/report` |
33
33
  | `ReportLead`, `ReportLeads`, `ReportGradeRow` | The shapes a monthly page's content is written in | none |
34
34
  | `Note`, `NoteProps` | The first touch set as a page: the opener, two lanes of findings, where the work would start, the close | `/notes` |
35
35
  | `NoteLane`, `NoteFinding`, `NoteStart`, `NoteStep`, `NoteClose`, `NoteLink`, `NoteParagraph` | The shapes a note's content is written in | none |
36
+ | `FirstCall`, `FirstCallProps` | The desk's sheet for the first call with a business that booked off a cold first touch: the brief with a picture beside it, the open as numbered steps, one unit per topic (a TL;DR, then show, ask, and move, each show line linked live with its picture opening the full capture), and the close, which always ends on who else decides. Not sent for now, but open like the host and shareable later, so it carries no desk instruction; it lives beside the writeup | `/first-call` |
37
+ | `firstCallLabels`, `FirstCallLabels` | The fixed words the sheet sets (Show, Ask, Move, the close's who else decides beat, and the rest), each overridable through `labels` | `/first-call` |
38
+ | `firstCallSectionIds` | The ids the page takes for itself (the shell's `main`, the brief, the open, the close), which a unit's `id` must not repeat; the content test checks it | none |
39
+ | `FirstCallFact`, `FirstCallStep`, `FirstCallShow`, `FirstCallUnit`, `FirstCallParagraph` | The shapes a first call sheet's content is written in, all plain data, so a skill writes the sheet as JSON | none |
36
40
  | `Gate`, `GateProps`, `GatePayload`, `GatePerson` | The sealed document, opened in the browser by the reader's own address, and the payload it opens | `/proposal` |
37
41
  | `normalizeEmail` | How a typed address becomes the key a payload opens with; the sealing command mirrors it | none |
38
42
  | `documents.css` | The documents' styles, at `@atelic-action/artifact/styles/documents.css` | every route |
@@ -136,6 +140,8 @@ Static files. `vercel.json` points Vercel at `dist/client` with no framework pre
136
140
 
137
141
  A document is fixed or grown here, in `src/documents/`, with its test in `tst/documents/`, and released as a version of this package; a folder takes it with `bun update @atelic-action/artifact`. A piece a document is built from (the grade chip, the sign off, a page component, the chrome, the wall, a command) is fixed in `@atelic-action/ui` and released there first, then taken here by bumping the ui range. An improvement to the seed itself (a route, a stub file, the example content) is made here and copied into the folders by hand.
138
142
 
143
+ A folder taking 3.0.0 moves three things in its own files, since 3.0.0 draws the read's scorecard and the writeup's overview as ui's `ReportCard` and removed the old markup with no alias. A table cell its content wrote as `<span className="rd-ranked">` becomes ui's `Rank` (`figure`, `tone`, `meaning`, `href`). A rule in its own sheets aimed at `.rd-gist-table`, `.rd-gist-card`, `.rd-gist-grade`, `.rd-gist-read`, `.rd-gist-section`, `.rd-gist-tier`, `.op-legend`, or any `.op-ov-` class matches nothing now, so it is rewritten against the card's `.rc-` classes or dropped. And a read's `labels` loses its `section` key, which the table's first column wore and the card has no use for.
144
+
139
145
  A folder taking 4.0.0 takes ui 3.0.0 with it (`bun update @atelic-action/artifact @atelic-action/ui --latest --cwd <short>`) and repoints its own routes, since ui 3.0.0 keeps acronyms capitalized with no alias for the old spelling: `canonicalUrl` from `@atelic-action/ui/artifact` becomes `canonicalURL` in `src/routes/index.tsx`, and the root route's `baseCss` import becomes `baseCSS`.
140
146
 
141
147
  ## Releasing
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@atelic-action/artifact",
3
- "version": "4.1.0",
4
- "description": "The composed documents the Atelic practice hands or sends a business (the writeup, the read, the monthly page, the note, and the sealed document), built from the pieces in @atelic-action/ui",
3
+ "version": "4.2.1",
4
+ "description": "The composed documents the Atelic practice hands or sends a business (the writeup, the read, the monthly page, the note, the first call sheet, and the sealed document), built from the pieces in @atelic-action/ui",
5
5
  "type": "module",
6
6
  "license": "MIT",
7
7
  "repository": {
@@ -0,0 +1,325 @@
1
+ import {
2
+ type ArtifactParagraph,
3
+ ArtifactAnchor as FirstCallAnchor,
4
+ Runs,
5
+ Shot,
6
+ type WriteupShot,
7
+ } from "@atelic-action/ui/artifact";
8
+ import { Callout, IconList } from "@atelic-action/ui/components";
9
+ import { Chip } from "@atelic-action/ui/primitives";
10
+ import { ArrowRight, MessageCircleQuestion } from "lucide-react";
11
+ import type { ReactNode } from "react";
12
+
13
+ /**
14
+ * The first call sheet: what the practice scans during the first call with a
15
+ * business that booked off a cold first touch. The brief, the open, then one
16
+ * unit per topic, each a show, an ask, and a move, then the close. It lives
17
+ * at `<short>.atelic.me/first-call` beside the writeup it walks.
18
+ *
19
+ * The practice uses it to guide the call and does not send it for now, but
20
+ * it is open like the rest of the host and may be shared with the business
21
+ * down the line, so it is written as a page they could read: everything
22
+ * on it is something we would show them or say to them, and it carries no
23
+ * desk instruction, no tracking, no prices, and no budget script. The method
24
+ * behind it (what goes in the brief, the open's three beats, which unit goes
25
+ * first, the posture in the room) is the practice repo's
26
+ * Outreach/discovery.md, The First Call Set, and nothing here restates it.
27
+ * The document owns only the shape.
28
+ *
29
+ * It is the writeup's and the read's sibling and wears their classes: the
30
+ * writeup's topic and findings blocks for the brief and each unit's show,
31
+ * the read's numbered steps for the open and the close. Its own few rules
32
+ * sit under fc- in documents.css. Every prop is plain data, so a skill can
33
+ * write a sheet as JSON. The close always ends on who else decides, the one
34
+ * beat every first call carries, set from the labels so no sheet can drop it.
35
+ */
36
+
37
+ /** A paragraph: a plain string, or runs of text and links in order. */
38
+ export type FirstCallParagraph = ArtifactParagraph;
39
+
40
+ /** One line of the brief: who, how they booked, the listing, the writeup. */
41
+ export interface FirstCallFact {
42
+ label: string;
43
+ body: FirstCallParagraph;
44
+ /** Sets the fact apart on a tinted ground, for the one that matters most. */
45
+ box?: boolean;
46
+ }
47
+
48
+ /** One numbered step of the open or the close. */
49
+ export interface FirstCallStep {
50
+ /** The step, set in bold. */
51
+ label: string;
52
+ /** The words under it, e.g. the question as it would be asked. */
53
+ text?: string;
54
+ }
55
+
56
+ /** One thing to show: the line, where it opens live, and the picture of it. */
57
+ export interface FirstCallShow {
58
+ text: string;
59
+ /** Where to open it on the call: in a new tab for an http(s) link, in place otherwise. */
60
+ href?: string;
61
+ /**
62
+ * The picture, set beside the show lines at thumbnail size and opening
63
+ * its `href` (the full capture behind a crop), or itself when there is none.
64
+ */
65
+ shot?: WriteupShot;
66
+ /** Reads as broken rather than as needing work, for the line that is plainly wrong. */
67
+ broken?: boolean;
68
+ }
69
+
70
+ /** The ids the page takes for itself (the shell's main, the sheet's sections), which no unit's id may repeat. */
71
+ export const firstCallSectionIds = ["main", "brief", "open", "close"] as const;
72
+
73
+ /** One topic: what we show, the questions it raises, the moves it leads to. */
74
+ export interface FirstCallUnit {
75
+ /** The anchor the unit chips jump to: lowercase, unique, and none of firstCallSectionIds. */
76
+ id: string;
77
+ title: string;
78
+ /** The unit in one breath, under its heading. */
79
+ tldr: string;
80
+ show: FirstCallShow[];
81
+ ask: string[];
82
+ move: string[];
83
+ }
84
+
85
+ /** The fixed words the sheet sets, each overridable through `labels`. */
86
+ export interface FirstCallLabels {
87
+ preparedFor: string;
88
+ brief: string;
89
+ open: string;
90
+ unitsNav: string;
91
+ tldr: string;
92
+ show: string;
93
+ ask: string;
94
+ move: string;
95
+ close: string;
96
+ /** The beat every close ends on. */
97
+ whoElse: string;
98
+ }
99
+
100
+ export const firstCallLabels: FirstCallLabels = {
101
+ preparedFor: "Prepared for",
102
+ brief: "The Brief",
103
+ open: "Open",
104
+ unitsNav: "Units",
105
+ tldr: "TL;DR",
106
+ show: "Show",
107
+ ask: "Ask",
108
+ move: "Move",
109
+ close: "Close",
110
+ whoElse: "Who else decides, and should they join the next call?",
111
+ };
112
+
113
+ export interface FirstCallProps {
114
+ title: string;
115
+ /** Who the call is with, set in the dateline: "Prepared for <person, business>". */
116
+ preparedFor: string;
117
+ /** The when line: the date, the 24 hour time, the place, the minutes booked. */
118
+ when: string;
119
+ brief: FirstCallFact[];
120
+ /** A picture beside the brief, e.g. the business's map card. */
121
+ briefShot?: WriteupShot;
122
+ open: FirstCallStep[];
123
+ /** One per topic, as many as the call has. */
124
+ units: FirstCallUnit[];
125
+ /** The close's steps; who else decides follows them. */
126
+ close: FirstCallStep[];
127
+ labels?: Partial<FirstCallLabels>;
128
+ }
129
+
130
+ /** The unit's number as the sheet sets it: 01, 02, and on. */
131
+ function unitNumber(index: number): string {
132
+ return String(index + 1).padStart(2, "0");
133
+ }
134
+
135
+ /** The read's numbered steps: the open and the close. */
136
+ function Steps({ steps }: { steps: FirstCallStep[] }) {
137
+ return (
138
+ <ol className="rd-steps">
139
+ {steps.map((step, i) => (
140
+ // biome-ignore lint/suspicious/noArrayIndexKey: steps are static content in a fixed order, and a label may repeat
141
+ <li className="rd-step" key={i}>
142
+ <div className="rd-step-body">
143
+ <p className="rd-step-head">
144
+ <b className="rd-step-title">{step.label}</b>
145
+ </p>
146
+ {step.text && <p>{step.text}</p>}
147
+ </div>
148
+ </li>
149
+ ))}
150
+ </ol>
151
+ );
152
+ }
153
+
154
+ /** The writeup's findings block: the callouts down the left, the pictures to the right. */
155
+ function Findings({
156
+ items,
157
+ shots,
158
+ block = false,
159
+ }: {
160
+ items: ReactNode[];
161
+ shots: WriteupShot[];
162
+ /** The block itself, as the writeup sets it, rather than inside a block under its heading. */
163
+ block?: boolean;
164
+ }) {
165
+ const classes = [block && "op-block", "op-findings", shots.length > 0 && "has-shots"];
166
+ return (
167
+ <div className={classes.filter(Boolean).join(" ")}>
168
+ <div className="op-findings-list">{items}</div>
169
+ {shots.length > 0 && (
170
+ <div className="op-shots">
171
+ {shots.map((shot, i) => (
172
+ // biome-ignore lint/suspicious/noArrayIndexKey: shots are static content in a fixed order, and a picture may repeat
173
+ <Shot shot={{ ...shot, href: shot.href ?? shot.src }} key={i} />
174
+ ))}
175
+ </div>
176
+ )}
177
+ </div>
178
+ );
179
+ }
180
+
181
+ function Unit({
182
+ unit,
183
+ index,
184
+ labels,
185
+ }: {
186
+ unit: FirstCallUnit;
187
+ index: number;
188
+ labels: FirstCallLabels;
189
+ }) {
190
+ const shows = unit.show.map((line, i) => (
191
+ <Callout
192
+ tone={line.broken ? "bad" : "warn"}
193
+ toneLabel={line.broken ? undefined : labels.show}
194
+ title={
195
+ line.href ? <FirstCallAnchor href={line.href}>{line.text}</FirstCallAnchor> : line.text
196
+ }
197
+ // biome-ignore lint/suspicious/noArrayIndexKey: show lines are static content in a fixed order
198
+ key={i}
199
+ />
200
+ ));
201
+ const shots = unit.show.flatMap((line) => (line.shot ? [line.shot] : []));
202
+ return (
203
+ <>
204
+ <div className="op-part" id={unit.id}>
205
+ <h2>
206
+ <span className="fc-unit-no">{unitNumber(index)}</span> {unit.title}
207
+ </h2>
208
+ </div>
209
+ <section className="op-topic">
210
+ <header className="op-topic-head">
211
+ <div>
212
+ <h3>{labels.tldr}</h3>
213
+ <p className="op-def">{unit.tldr}</p>
214
+ </div>
215
+ </header>
216
+ <div className="op-blocks">
217
+ {shows.length > 0 && (
218
+ <div className="op-block rd-beat fc-show">
219
+ <h4>{labels.show}</h4>
220
+ <Findings items={shows} shots={shots} />
221
+ </div>
222
+ )}
223
+ {unit.ask.length > 0 && (
224
+ <div className="op-block rd-beat fc-ask">
225
+ <h4>{labels.ask}</h4>
226
+ <IconList
227
+ icon={<MessageCircleQuestion size={16} />}
228
+ items={unit.ask.map((text) => ({ body: text }))}
229
+ />
230
+ </div>
231
+ )}
232
+ {unit.move.length > 0 && (
233
+ <div className="op-block rd-beat fc-move">
234
+ <h4>{labels.move}</h4>
235
+ <IconList
236
+ icon={<ArrowRight size={16} strokeWidth={2.5} />}
237
+ items={unit.move.map((text) => ({ body: text }))}
238
+ />
239
+ </div>
240
+ )}
241
+ </div>
242
+ </section>
243
+ </>
244
+ );
245
+ }
246
+
247
+ export function FirstCall({
248
+ title,
249
+ preparedFor,
250
+ when,
251
+ brief,
252
+ briefShot,
253
+ open,
254
+ units,
255
+ close,
256
+ labels: overrides,
257
+ }: FirstCallProps) {
258
+ const labels = { ...firstCallLabels, ...overrides };
259
+ const facts = brief.map((fact, i) => (
260
+ <Callout
261
+ tone="info"
262
+ variant={fact.box ? "box" : "plain"}
263
+ title={fact.label}
264
+ // biome-ignore lint/suspicious/noArrayIndexKey: the brief is static content in a fixed order
265
+ key={i}
266
+ >
267
+ <Runs paragraph={fact.body} />
268
+ </Callout>
269
+ ));
270
+ return (
271
+ <div className="op rd fc">
272
+ <article className="op-doc rd-doc">
273
+ <header className="op-head">
274
+ <p className="op-meta">
275
+ {labels.preparedFor} <b>{preparedFor}</b>
276
+ <span className="op-meta-sep" aria-hidden="true">
277
+ {" · "}
278
+ </span>
279
+ <span className="op-meta-date">{when}</span>
280
+ </p>
281
+ <h1>{title}</h1>
282
+ </header>
283
+
284
+ {facts.length > 0 && (
285
+ <section className="op-topic" id="brief">
286
+ <header className="op-topic-head">
287
+ <div>
288
+ <h3>{labels.brief}</h3>
289
+ </div>
290
+ </header>
291
+ <div className="op-blocks">
292
+ <Findings items={facts} shots={briefShot ? [briefShot] : []} block />
293
+ </div>
294
+ </section>
295
+ )}
296
+
297
+ {open.length > 0 && (
298
+ <section className="rd-plan" id="open">
299
+ <h2>{labels.open}</h2>
300
+ <Steps steps={open} />
301
+ </section>
302
+ )}
303
+ {/* Outside the read's plan section, whose link rule would restyle every chip as a prose link. */}
304
+ {units.length > 1 && (
305
+ <nav className="fc-units" aria-label={labels.unitsNav}>
306
+ {units.map((unit, i) => (
307
+ <Chip dot href={`#${unit.id}`} key={unit.id}>
308
+ {i + 1}. {unit.title}
309
+ </Chip>
310
+ ))}
311
+ </nav>
312
+ )}
313
+
314
+ {units.map((unit, i) => (
315
+ <Unit unit={unit} index={i} labels={labels} key={unit.id} />
316
+ ))}
317
+
318
+ <section className="rd-plan" id="close">
319
+ <h2>{labels.close}</h2>
320
+ <Steps steps={[...close, { label: labels.whoElse }]} />
321
+ </section>
322
+ </article>
323
+ </div>
324
+ );
325
+ }
@@ -34,12 +34,12 @@ import { Topic, type WriteupTopic } from "./Writeup";
34
34
  *
35
35
  * It wears the writeup's column, head, grade chip and key, tier heading,
36
36
  * section card, and close (the `.op` classes), and adds only what is its own
37
- * (the `.rd` classes). The gist table is drawn from the tiers, so a tier
37
+ * (the `.rd` classes). The scorecard is drawn from the tiers, so a tier
38
38
  * added later (Conversion, once the analytics access lands) is one more entry
39
- * in `tiers` and appears in the table and the body alike; until then `note`
40
- * says so under the table. Every prop but a finding's body and the sign off
41
- * person's `role` is plain data, and both are equally happy with a string, so
42
- * a routine can write a read as JSON.
39
+ * in `tiers` and appears in the scorecard and the body alike; until then
40
+ * `note` says so over the scorecard. Every prop but a finding's body and the
41
+ * sign off person's `role` is plain data, and both are equally happy with a
42
+ * string, so a routine can write a read as JSON.
43
43
  */
44
44
 
45
45
  /** A link inside running text: the words the reader clicks and where they go. */
@@ -57,10 +57,10 @@ export interface ReadChange {
57
57
  /**
58
58
  * One section of a tier: the writeup's section card (its `title`, `href`,
59
59
  * `oneLiner`, `grade`, `why`, `blocks`, and `sources`), plus the read's own:
60
- * the gist table's line, today and after, and what we recommend.
60
+ * the scorecard's line, today and after, and what we recommend.
61
61
  */
62
62
  export interface ReadSection extends Omit<WriteupTopic, "kind" | "why"> {
63
- /** The one line in the gist table. */
63
+ /** The one line on the scorecard. */
64
64
  gist: ReadParagraph;
65
65
  /** The summary of what we saw, in one or two sentences: the why line. A paragraph, so a read written as JSON can carry links in it. */
66
66
  why?: ReactNode | ReadParagraph;
@@ -126,9 +126,9 @@ export interface ReadClose {
126
126
  export interface ReadLabels {
127
127
  /** The dateline's words before the business, e.g. "Prepared for". */
128
128
  preparedFor: string;
129
- /** What a screen reader calls the gist card when it has no title. */
129
+ /** What a screen reader calls the scorecard when it has no title. */
130
130
  gist: string;
131
- /** What a screen reader hears before a letter, on the gist card and each section's card. */
131
+ /** What a screen reader hears before a letter, on the scorecard and each section's card. */
132
132
  grade: string;
133
133
  /** What a screen reader hears for a section with no grade. */
134
134
  notGraded: string;
@@ -169,15 +169,15 @@ export interface ReadProps {
169
169
  lede: ReadParagraph;
170
170
  /** The heading over the scorecard, e.g. "The Scorecard". */
171
171
  gistTitle?: string;
172
- /** The line under the scorecard's heading, e.g. what it is. */
172
+ /** The line under the scorecard's heading, e.g. what it is. Set only with a `gistTitle`, since it reads as the heading's subtitle. */
173
173
  gistLead?: string;
174
- /** The anchor a header stop points at for the scorecard, e.g. "scorecard". */
174
+ /** The anchor a header stop points at for the scorecard, e.g. "scorecard": on the heading with a `gistTitle`, on the card itself without one. */
175
175
  gistId?: string;
176
176
  /** A callout over the scorecard, under its heading, e.g. what the read leaves out and when it follows. */
177
177
  note?: ReadParagraph;
178
178
  /** The key: what each letter means, in a few words each. */
179
179
  gradeKey?: Array<{ grade: WriteupGrade; label: string }>;
180
- /** The tiers in order. Each section appears in the gist table and in the body. */
180
+ /** The tiers in order. Each section appears on the scorecard and in the body. */
181
181
  tiers: ReadTier[];
182
182
  plan: ReadPlan;
183
183
  close: ReadClose;
@@ -294,6 +294,7 @@ function Gist({
294
294
  </div>
295
295
  )}
296
296
  <ReportCard
297
+ id={title ? undefined : id}
297
298
  className="rd-gist"
298
299
  label={title ?? labels.gist}
299
300
  legend={gradeKey}
@@ -1,10 +1,10 @@
1
1
  /* The composed documents in this package: the sealed document stage (Gate),
2
- the monthly page (Report), the writeup (Writeup), the note (Note), and the
3
- read (Read). Moved from @atelic-action/ui's styles/artifact.css with the
2
+ the monthly page (Report), the writeup (Writeup), the note (Note), the
3
+ read (Read), and the first call sheet (FirstCall). Moved from @atelic-action/ui's styles/artifact.css with the
4
4
  documents (ATE-671), in the order that sheet bundled them, which the two
5
5
  @page rules depend on: the later one wins, and the writeup's is the later
6
- one. The note and the read follow the writeup and set no @page, so the
7
- writeup's stays the one that wins.
6
+ one. The note, the read, and the first call sheet follow the writeup and
7
+ set no @page, so the writeup's stays the one that wins.
8
8
 
9
9
  The pieces the documents share stay in @atelic-action/ui: the grade chip
10
10
  (.op-grade), the sign off (.op-who, .op-sign), the shot (.op-shot), the
@@ -2036,7 +2036,7 @@
2036
2036
  column, the head, the grade chip and its key, the tier heading, the
2037
2037
  section card, the screen, the close, and every print rule of the
2038
2038
  writeup's are its own already. This block adds only what the read has
2039
- and the writeup does not: the gist table and the note under it, the
2039
+ and the writeup does not: the scorecard and the note over it, the
2040
2040
  two beats a section adds to the writeup's card (today and after, and
2041
2041
  what we recommend), and the numbered plan. It sets no @page; the writeup's, earlier in this sheet and the
2042
2042
  later of the two there, sets letter paper for every artifact page.
@@ -2065,6 +2065,7 @@
2065
2065
  /* ---- the gist: the ReportCard (@atelic-action/ui), and this is its place on the page ---- */
2066
2066
  .atelic-ui .rd-gist {
2067
2067
  margin-top: 26px;
2068
+ scroll-margin-top: calc(var(--nav-height) + 16px);
2068
2069
  }
2069
2070
  .atelic-ui .op-part + .rd-gist {
2070
2071
  margin-top: 0;
@@ -2090,7 +2091,7 @@
2090
2091
  .atelic-ui .rd-beat p:last-child {
2091
2092
  margin-bottom: 0;
2092
2093
  }
2093
- /* The note under the gist table: what the read leaves out, on a tinted panel with the accent's rule. */
2094
+ /* The note over the scorecard: what the read leaves out, on a tinted panel with the accent's rule. */
2094
2095
  .atelic-ui .rd-note {
2095
2096
  margin-top: 18px;
2096
2097
  padding: 12px 14px;
@@ -2385,4 +2386,41 @@
2385
2386
  break-after: avoid;
2386
2387
  }
2387
2388
  }
2389
+
2390
+ /* ---- the first call sheet (FirstCall): the writeup's topic and findings
2391
+ blocks and the read's steps carry it; these are the few rules it adds
2392
+ (the Claude Design cut of 2026-10-23) ---- */
2393
+ .atelic-ui .fc-units {
2394
+ display: flex;
2395
+ flex-wrap: wrap;
2396
+ gap: 8px;
2397
+ margin-top: 18px;
2398
+ }
2399
+ /* A long unit title wraps rather than pushing the phone page sideways. */
2400
+ .atelic-ui .fc-units .chip {
2401
+ white-space: normal;
2402
+ }
2403
+ .atelic-ui .fc-unit-no {
2404
+ font-family: var(--font-mono);
2405
+ font-weight: 500;
2406
+ color: var(--primary);
2407
+ margin-right: 6px;
2408
+ }
2409
+ .atelic-ui .fc .fc-ask .icon-list {
2410
+ --tone: var(--ink);
2411
+ }
2412
+ .atelic-ui .fc .fc-move .icon-list {
2413
+ --tone: var(--primary);
2414
+ }
2415
+ /* A show line links to where it opens live, but reads as a plain callout:
2416
+ the link takes the ink and shows itself on hover. */
2417
+ .atelic-ui .fc-show .callout-title a {
2418
+ color: inherit;
2419
+ text-decoration: none;
2420
+ }
2421
+ .atelic-ui .fc-show .callout-title a:hover,
2422
+ .atelic-ui .fc-show .callout-title a:focus-visible {
2423
+ color: var(--primary);
2424
+ text-decoration: underline;
2425
+ }
2388
2426
  }
@@ -4,6 +4,7 @@
4
4
  * chip, the sign off, the page components, the button), which this package
5
5
  * depends on and never copies, and wears the styles in ./documents.css.
6
6
  */
7
+ export * from "./FirstCall";
7
8
  export { Gate, type GatePayload, type GatePerson, type GateProps, normalizeEmail } from "./Gate";
8
9
  export * from "./Note";
9
10
  export * from "./Read";