@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 +9 -3
- package/package.json +2 -2
- package/src/documents/FirstCall.tsx +325 -0
- package/src/documents/Read.tsx +13 -12
- package/src/documents/documents.css +44 -6
- package/src/documents/index.ts +1 -0
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
|
-
|
|
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
|
|
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
|
|
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
|
+
}
|
package/src/documents/Read.tsx
CHANGED
|
@@ -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
|
|
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
|
|
40
|
-
* says so
|
|
41
|
-
* person's `role` is plain data, and both are equally happy with a
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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),
|
|
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
|
|
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
|
|
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
|
|
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
|
}
|
package/src/documents/index.ts
CHANGED
|
@@ -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";
|