@atelic-action/ui 1.0.0 → 2.0.0

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
@@ -6,7 +6,7 @@ The package ships source, not a build. Its TSX and CSS arrive as written and com
6
6
 
7
7
  ## Component Index
8
8
 
9
- Every export, grouped by the entry it is imported from. The gallery column links the section of [ui.atelic.me](https://ui.atelic.me) that shows it; none marks an export that draws nothing.
9
+ Every export, grouped by the entry it is imported from. The gallery column links the section of [ui.atelic.me](https://ui.atelic.me) that shows it; none marks an export that draws nothing. The composed documents (the writeup, the read, the monthly page, the note, and the sealed document) are not here: since 2.0.0 they live in [`@atelic-action/artifact`](https://github.com/atelic-action/artifact), which builds them from the pieces this package exports.
10
10
 
11
11
  | Export | Entry | What it is | Gallery |
12
12
  |---|---|---|---|
@@ -32,11 +32,10 @@ Every export, grouped by the entry it is imported from. The gallery column links
32
32
  | `Watermark` | `@atelic-action/ui/components` | The signed in reader's confidential stamp, pill or tiled | [#watermark](https://ui.atelic.me/#watermark) |
33
33
  | `readCookie`, `Tone` | `@atelic-action/ui/components` | The cookie reader the watermark uses, and the tone type | none |
34
34
  | `Button`, `Chip`, `Eyebrow`, `Lead`, `SectionHeading` | `@atelic-action/ui/primitives` | The small pieces a page is set in | [#primitives](https://ui.atelic.me/#primitives) |
35
- | `Writeup`, `ratingCell` | `@atelic-action/ui/artifact` | The graded document an owner gets, and its star rating table cell | [#writeup](https://ui.atelic.me/#writeup) |
36
- | `Report` | `@atelic-action/ui/artifact` | The monthly page an Operate client gets | [#report](https://ui.atelic.me/#report) |
37
- | `Note` | `@atelic-action/ui/artifact` | The first touch page, for a business with no published email | [#note](https://ui.atelic.me/#note) |
38
- | `Gate`, `normalizeEmail` | `@atelic-action/ui/artifact` | The sealed document, opened in the browser by the reader's own address | [#gate](https://ui.atelic.me/#gate) |
39
- | `GradeChip`, `gradeLabel`, `SignOff` | `@atelic-action/ui/artifact` | The grade letter and the sign off the documents share | [#writeup](https://ui.atelic.me/#writeup) |
35
+ | `GradeChip`, `gradeLabel`, `WriteupGrade` | `@atelic-action/ui/artifact` | A section's grade, A to D, as a chip in its color, the letter alone, and the grade type | [#grade-chip](https://ui.atelic.me/#grade-chip) |
36
+ | `SignOff`, `WriteupPerson` | `@atelic-action/ui/artifact` | The close a document ends on, and the person it names | [#sign-off](https://ui.atelic.me/#sign-off) |
37
+ | `Shot`, `WriteupShot` | `@atelic-action/ui/artifact` | The owner's own screen, opening the full picture, over its caption | [#shot](https://ui.atelic.me/#shot) |
38
+ | `Paragraph`, `Runs`, `ArtifactAnchor`, `isEmpty`, `ArtifactLink`, `ArtifactParagraph` | `@atelic-action/ui/artifact` | Plain data text a routine can write as JSON: a paragraph, its runs alone, one link, the empty test, and the two types | [#paragraph](https://ui.atelic.me/#paragraph) |
40
39
  | `ArtifactShell` | `@atelic-action/ui/artifact` | The chrome around a document page: skip link, dark header, credit band, watermark | [#site-header](https://ui.atelic.me/#site-header) (its dark bar) |
41
40
  | `AppErrorBoundary`, `ErrorFallback` | `@atelic-action/ui/artifact` | The error boundary and the page it falls back to | none |
42
41
  | `buildPageHead`, `canonicalUrl`, `newTabProps`, the `ArtifactConfig` types | `@atelic-action/ui/artifact` | A page's head, a link's new tab props, and a site's contract | none |
@@ -125,10 +124,12 @@ Prerendering and server rendering have to compile the package too, so keep it ou
125
124
  ```ts
126
125
  // vite.config.ts
127
126
  export default defineConfig({
128
- ssr: { noExternal: ["@atelic-action/ui"] },
127
+ ssr: { noExternal: ["@atelic-action/ui", "@atelic-action/artifact"] },
129
128
  });
130
129
  ```
131
130
 
131
+ A site that renders a document needs `@atelic-action/artifact` kept internal the same way, since it too ships TSX and CSS as written; a site that renders none lists `@atelic-action/ui` alone.
132
+
132
133
  ## CSS Import Order
133
134
 
134
135
  Import the stylesheets in this order, from the root route or the site's base sheet:
@@ -140,8 +141,9 @@ Import the stylesheets in this order, from the root route or the site's base she
140
141
  5. `@atelic-action/ui/styles/primitives.css`
141
142
  6. `@atelic-action/ui/styles/gate.css`, on a site that mounts the access gate
142
143
  7. `@atelic-action/ui/styles/artifact.css`, on a site that hosts the artifact documents
143
- 8. The site's own CSS
144
- 9. The site's `theme.css`, last
144
+ 8. `@atelic-action/artifact/styles/documents.css`, on the same site, for the documents themselves
145
+ 9. The site's own CSS
146
+ 10. The site's `theme.css`, last
145
147
 
146
148
  Every rule in the package sheets is scoped under `.atelic-ui`, the one root class a consumer wears: a site puts it on its outermost wrapper, an app on the region that holds a package component. Which sheets it imports decides what it gets, so a site importing `base.css` gets the resets and typography under that class and an app importing `components.css` alone gets none of them.
147
149
 
@@ -329,9 +331,9 @@ The two pieces a reader sees are ordinary components, in `@atelic-action/ui/comp
329
331
 
330
332
  ## The Artifact Documents
331
333
 
332
- `@atelic-action/ui/artifact` is everything the artifact template shared across its cuts, so a fix reaches every business's documents with one version bump:
334
+ `@atelic-action/ui/artifact` is the pieces an artifact document is built from and the site around it, so a fix to a piece reaches every business's documents with one version bump. The composed documents themselves (`Writeup`, `Read`, `Report`, `Note`, and the sealed `Gate`, with their content types and their stylesheet) live in [`@atelic-action/artifact`](https://github.com/atelic-action/artifact) since 2.0.0, which depends on this package for everything below:
333
335
 
334
- - **The documents:** `Writeup`, `Report`, `Note`, and `Gate` (the sealed document, with its `GatePayload` type; a reader's address is the key, so a payload never lists it, and the component identifies a reader to HubSpot by the address they typed), plus the `GradeChip` and `SignOff` they share, and every content type they take (`WriteupProps`, `ReportProps`, `NoteProps`, `GateProps`, and the rest).
336
+ - **The pieces:** `GradeChip` and `gradeLabel` with the `WriteupGrade` type, `SignOff` with the `WriteupPerson` type, `Shot` with the `WriteupShot` type, and the plain data text (`Paragraph`, `Runs`, `ArtifactAnchor`, and `isEmpty`, with the `ArtifactLink` and `ArtifactParagraph` types: a paragraph is a string or runs of text and `{ text, href }` links, so a routine can write a document as JSON).
335
337
  - **The shell:** `ArtifactShell`, the `.atelic-ui` scope with the skip link, the dark header, the credit band, and the watermark around a page.
336
338
  - **The contract and the head:** the `ArtifactConfig` types, and `buildPageHead` and `canonicalUrl` with their `PageMeta` type.
337
339
  - **Who opened it:** `mintToken`, `sealReader`, `openReader`, `readerId`, `readerLink`, and `whoIsReading`. The same six are also built and exported alone as `@atelic-action/ui/artifact/readers`, because a Playwright spec and a script run under Node, which compiles nothing inside `node_modules`.
@@ -350,11 +352,7 @@ export function ArtifactShell(props: Omit<ArtifactShellProps, "currentPath">) {
350
352
  }
351
353
  ```
352
354
 
353
- **The note** is the first touch for a business whose site publishes no email address, served at `/notes` and wrapped in `ArtifactShell` exactly as the writeup is. It is the writeup's sibling, not a variant: a title and a dateline, the opener, two lanes of findings (what the customer sees, what sits under the hood), the steps the work would start with, and the close over the shared `SignOff`. Every prop is plain data, so a routine can write a note as JSON; a paragraph is a string or an array of strings and `{ text, href }` links, and the sign off person's `role` is the one field that also takes a node. A finding is a `Callout` with its `WriteupShot` under it. Its column is the writeup's 920px, centered under the header with the header's `--gutter`, and its text runs the column's full width. A finding sets its screen under its words on a phone and beside them, on the right, when wide and on paper. It reads on a phone first, stacks the two lanes as separate sections top to bottom at every width and on paper, never side by side, and prints to one letter sheet under the writeup's `@page`.
354
-
355
- **The sealed document's classes.** `Gate` renders the opened document as the HTML it was sealed from, so its markup is the content's, and `artifact.css` styles what that HTML may wear inside `.gate-content`: the tier row (`.gate-tiers` of `.gate-tier` cards, `.is-picked` on the recommended one, `.gate-tier-name`, `.gate-tier-price`, `.gate-tier-note`, and the `.gate-tier-math` sum column of `.gate-tier-was`, `.gate-tier-credit`, `.gate-tier-mathlabel`, and the price), `ul.icon-list` for a list led by inline lucide icons (sized for the card inside a tier), `ul.gate-checks`, and, from 1.0.0 (ATE-599, pulled up from the Wynkoop and Outdoors Geek hosts), the `.gate-addons` row that seats two `.gate-tier.is-addon` monthly cards side by side under the builds, an inline SVG mark inside `.gate-tier-mathlabel` that stays on the label's line, and the `.gate-footnote` line under the tiers that the mark points at. A folder stylesheet that carried any of these deletes it.
356
-
357
- The documents import no stylesheet of their own. A site imports `@atelic-action/ui/styles/artifact.css` in its base sheet after `gate.css` and before its `theme.css`, so every page carries the rules whichever route it is. The sheet holds two `@page` rules, the monthly page's and then the writeup's, and the later one wins for every page a site prints; the order inside the sheet is the order the template's build bundled them in and is load bearing.
355
+ The pieces import no stylesheet of their own. A site imports `@atelic-action/ui/styles/artifact.css` in its base sheet after `gate.css`, then `@atelic-action/artifact/styles/documents.css`, and then its `theme.css`, so every page carries the rules whichever route it is. This sheet holds the pieces alone (the grade chip, the sign off, the shot, and the shell's logo rule) and sets no `@page`; both `@page` rules, the monthly page's and then the writeup's, moved with the documents into their sheet, where their order stays load bearing.
358
356
 
359
357
  ### The Artifact Vite Config
360
358
 
@@ -367,7 +365,7 @@ import { artifactConfig } from "@atelic-action/ui/vite";
367
365
  export default artifactConfig({ pages: ["/writeup", "/proposal", "/report"] });
368
366
  ```
369
367
 
370
- `artifactConfig` returns the TanStack Start plugin with prerendering on, the crawler following links but kept out of `/images/` (where a prerendered page would overwrite a real file), one prerendered page for `/`, for each path the site names, and for `/404`, and no sitemap; the React plugin; `privateChunks()`; the `@` alias to the site's `src`, resolved from the directory Vite is run in; and `ssr.noExternal: ["@atelic-action/ui"]`. Nothing links to an artifact page, so a page a site leaves out of `pages` ships with no HTML in it. `artifactPages` is the list alone.
368
+ `artifactConfig` returns the TanStack Start plugin with prerendering on, the crawler following links but kept out of `/images/` (where a prerendered page would overwrite a real file), one prerendered page for `/`, for each path the site names, and for `/404`, and no sitemap; the React plugin; `privateChunks()`; the `@` alias to the site's `src`, resolved from the directory Vite is run in; and `ssr.noExternal: ["@atelic-action/ui", "@atelic-action/artifact"]`, so the documents compile too. Nothing links to an artifact page, so a page a site leaves out of `pages` ships with no HTML in it. `artifactPages` is the list alone.
371
369
 
372
370
  A site with a real need of its own passes `extend`, which is merged over the result with Vite's `mergeConfig`:
373
371
 
@@ -399,7 +397,7 @@ The package installs the scripts every artifact site used to carry as files, so
399
397
  | `atelic-reader-link <email> [path] [--token <token>] [--qr \| --no-qr]` | Mints a named reader: prints the sealed line for `analytics.identify` and the link to send, and writes `public/images/qr.png` for a public writeup's first reader | `reader` |
400
398
  | `atelic-serve-static` | Serves `dist/client` on `127.0.0.1` the way Vercel does, on `PORT` or 4173 | The `webServer` command in `playwright.config.ts` |
401
399
  | `atelic-print-sheets <url or dist/client> <out.pdf> [sheets] [page] [port]` | Prints a page to PDF with headless Chrome, reports the sheet count, and fails when a count is given and missed | `print` |
402
- | `atelic-seal-document <config.json> [out.json]` | Seals a document into the payload `Gate` reads, one wrapped key for each reader. The payload names no one: no address, and readers numbered `r1`, `r2` whatever the config calls them | `seal` |
400
+ | `atelic-seal-document <config.json> [out.json]` | Seals a document into the payload that `@atelic-action/artifact`'s `Gate` reads, one wrapped key for each reader. The payload names no one: no address, and readers numbered `r1`, `r2` whatever the config calls them | `seal` |
403
401
  | `atelic-clean-sitemap` | Strips fragment and trailing slash duplicates out of `dist/client/sitemap.xml`; says so and passes when a based build wrote none | A marketing site's `build`, after vite |
404
402
  | `atelic-check-base-path` | Fails the build when a root path in `dist/client` lacks the base path, or `vercel.json` lacks (or strays from) the two rewrites a based cut needs; a `VITE_BASE_PATH` rehearsal only warns about `vercel.json` off Vercel | Last step of a marketing site's `build` |
405
403
  | `atelic-contact-canary` | Posts a token bearing test message to `$SITE_URL/api/contact` and passes only on a Resend id and the expected destination (`CONTACT_EXPECTED_TO`, `CONTACT_CANARY_TOKEN`); a `.invalid` `SITE_URL` is the off switch | `check:contact`, run daily by the canary workflow |
@@ -889,14 +887,14 @@ An `expiresAt` an hour out keeps the refresh path quiet; one inside five minutes
889
887
 
890
888
  ## The Gallery
891
889
 
892
- `gallery/` is two static pages. The Components shows everything the package exports that draws (the chrome, the page components, the sections, the documents, and the email) on its own with made up data for a fictional bakery, wearing the practice's own palette and type. It lives at [ui.atelic.me](https://ui.atelic.me), noindexed and disallowed in its `robots.txt`, since it is for the people building with the package and never search inventory. It imports the components and stylesheets from `src/` by relative path rather than from npm, so it always shows the code in the repo, and it reads the package name and version from `package.json` at build time. It sits outside `src/`, so it is never published. The second page, [ui.atelic.me/app](https://ui.atelic.me/app) (`gallery/app.html`), runs the app shell for a made up bakery's ops app, on Tailwind through `@tailwindcss/vite`, with its own stylesheet set up the way an app's `globals.css` is; the plugin leaves The Components' plain sheets as they are.
890
+ `gallery/` is two static pages. The Components shows everything the package exports that draws (the chrome, the page components, the sections, the pieces a document is built from, and the email) on its own with made up data for a fictional bakery, wearing the practice's own palette and type. It lives at [ui.atelic.me](https://ui.atelic.me), noindexed and disallowed in its `robots.txt`, since it is for the people building with the package and never search inventory. It imports the components and stylesheets from `src/` by relative path rather than from npm, so it always shows the code in the repo, and it reads the package name and version from `package.json` at build time. It sits outside `src/`, so it is never published. The second page, [ui.atelic.me/app](https://ui.atelic.me/app) (`gallery/app.html`), runs the app shell for a made up bakery's ops app, on Tailwind through `@tailwindcss/vite`, with its own stylesheet set up the way an app's `globals.css` is; the plugin leaves The Components' plain sheets as they are.
893
891
 
894
892
  ```bash
895
893
  bun run gallery:dev # a dev server on the repo's own source
896
894
  bun run gallery:build # the static page, written to gallery/dist
897
895
  ```
898
896
 
899
- `vercel.json` builds it the same way and serves `gallery/dist`. Typecheck and lint cover it, and CI builds it, so a component change that breaks the gallery fails CI. **A new component gets a gallery section in the same change**, with each of its meaningfully different forms shown. `gallery/parts.tsx` holds the frame pieces (`Piece`, `Example`, and `Frame`, which holds a fixed piece inside the page), and a group of pieces lives in a file of its own beside `Gallery.tsx` (`chrome.tsx`, `documents.tsx`, `email.tsx`), mounted there with one element. Each document is one whole fictional document, and the sealed one was sealed with the package's own sealing command (`gallery/gate-payload.json`, reader `reader@example.test`).
897
+ `vercel.json` builds it the same way and serves `gallery/dist`. Typecheck and lint cover it, and CI builds it, so a component change that breaks the gallery fails CI. **A new component gets a gallery section in the same change**, with each of its meaningfully different forms shown. `gallery/parts.tsx` holds the frame pieces (`Piece`, `Example`, and `Frame`, which holds a fixed piece inside the page), and a group of pieces lives in a file of its own beside `Gallery.tsx` (`chrome.tsx`, `artifact.tsx`, `email.tsx`), mounted there with one element. The composed documents are shown by `@atelic-action/artifact`, through its own stubs.
900
898
 
901
899
  ## Releasing
902
900
 
@@ -33,7 +33,8 @@ export function artifactConfig({ pages, extend }) {
33
33
  const config = {
34
34
  // Every stylesheet rides in the one base sheet the root route links
35
35
  // (src/styles/base.css), the documents' own included, since they come from
36
- // @atelic-action/ui. Nothing imports CSS from a component, so there is no
36
+ // @atelic-action/artifact and its pieces from @atelic-action/ui. Nothing
37
+ // imports CSS from a component, so there is no
37
38
  // component bundle to fold: `build.cssCodeSplit: false`, which sat here to
38
39
  // fold it, now fails the build, because TanStack Start looks for the
39
40
  // `style.css` that setting emits and none is written. A site that gives a
@@ -73,10 +74,11 @@ export function artifactConfig({ pages, extend }) {
73
74
  "@": resolve(process.cwd(), "./src"),
74
75
  },
75
76
  },
76
- // @atelic-action/ui ships TSX and CSS as written, so the prerender's server build
77
- // has to compile it rather than hand it to Node as an external.
77
+ // @atelic-action/ui and @atelic-action/artifact (the documents) both ship
78
+ // TSX and CSS as written, so the prerender's server build has to compile
79
+ // them rather than hand them to Node as externals.
78
80
  ssr: {
79
- noExternal: ["@atelic-action/ui"],
81
+ noExternal: ["@atelic-action/ui", "@atelic-action/artifact"],
80
82
  },
81
83
  };
82
84
  return extend ? mergeConfig(config, extend) : config;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atelic-action/ui",
3
- "version": "1.0.0",
3
+ "version": "2.0.0",
4
4
  "description": "Shared UI for the Atelic templates: site chrome, base styles, the component library, the access gate, and the app shell they install",
5
5
  "type": "module",
6
6
  "license": "UNLICENSED",
@@ -16,9 +16,10 @@ export const gradeLabel: Record<WriteupGrade, string> = {
16
16
  };
17
17
 
18
18
  /**
19
- * The letter as a chip in its grade's color. The writeup and the monthly page
20
- * both wear it, so its styles live in the base sheet chain
21
- * (styles/artifact.css), never in either component's stylesheet.
19
+ * The letter as a chip in its grade's color. The documents in
20
+ * @atelic-action/artifact (the writeup, the read, the monthly page) wear it,
21
+ * so its styles live in the base sheet chain (styles/artifact.css), never in
22
+ * a document's stylesheet.
22
23
  */
23
24
  export function GradeChip({ grade }: { grade: WriteupGrade }) {
24
25
  return (
@@ -0,0 +1,65 @@
1
+ import { newTabProps } from "../lib/newTabProps";
2
+
3
+ /**
4
+ * Plain data text for the documents in @atelic-action/artifact a routine
5
+ * can write as JSON (the note and the read): a paragraph is a string, or runs of text and links in
6
+ * order. A link opens in a new tab when it leaves the page, and in place
7
+ * when it is an anchor or a mail link.
8
+ */
9
+
10
+ /** A link inside running text: the words the reader clicks and where they go. */
11
+ export interface ArtifactLink {
12
+ text: string;
13
+ href: string;
14
+ }
15
+
16
+ /** A paragraph: a plain string, or runs of text and links in order. */
17
+ export type ArtifactParagraph = string | Array<string | ArtifactLink>;
18
+
19
+ /** A link opens in a new tab when it leaves the page, and in place when it is an anchor or a mail link. */
20
+ export function ArtifactAnchor({ href, children }: { href: string; children: string }) {
21
+ return (
22
+ <a href={href} {...newTabProps(href, /^https?:/i.test(href))}>
23
+ {children}
24
+ </a>
25
+ );
26
+ }
27
+
28
+ /** A paragraph with nothing to read: a blank string, or runs that are all blank strings. A link always counts. */
29
+ export function isEmpty(paragraph: ArtifactParagraph): boolean {
30
+ const runs = typeof paragraph === "string" ? [paragraph] : paragraph;
31
+ return runs.every((run) => typeof run === "string" && run.trim() === "");
32
+ }
33
+
34
+ /** The runs alone, for a container that is not a paragraph (a list item, a table cell). */
35
+ export function Runs({ paragraph }: { paragraph: ArtifactParagraph }) {
36
+ const runs = typeof paragraph === "string" ? [paragraph] : paragraph;
37
+ return (
38
+ <>
39
+ {runs.map((run, i) =>
40
+ typeof run === "string" ? (
41
+ run
42
+ ) : (
43
+ // biome-ignore lint/suspicious/noArrayIndexKey: runs are static content in a fixed order
44
+ <ArtifactAnchor href={run.href} key={i}>
45
+ {run.text}
46
+ </ArtifactAnchor>
47
+ ),
48
+ )}
49
+ </>
50
+ );
51
+ }
52
+
53
+ export function Paragraph({
54
+ paragraph,
55
+ className,
56
+ }: {
57
+ paragraph: ArtifactParagraph;
58
+ className?: string;
59
+ }) {
60
+ return (
61
+ <p className={className}>
62
+ <Runs paragraph={paragraph} />
63
+ </p>
64
+ );
65
+ }
@@ -12,10 +12,12 @@ export interface WriteupShot {
12
12
 
13
13
  /**
14
14
  * The owner's own screen: the crop, opening the full screenshot in a new tab
15
- * when it has one, over its caption. The writeup and the note both wear it;
16
- * its styles (.op-shot) are the writeup's, and the note only sizes it. A
17
- * linked crop says it opens: a hover title, and an expand badge the note
18
- * shows on its thumbnails and the writeup keeps hidden.
15
+ * when it has one, over its caption. The writeup, the read, and the note in
16
+ * @atelic-action/artifact all wear it, so its styles (.op-shot) live in the
17
+ * base sheet chain (styles/artifact.css), and each document sizes it in its
18
+ * own sheet. A linked crop says it opens: a hover title, and an expand badge
19
+ * hidden by default that a document may turn on (the note does, on its
20
+ * thumbnails).
19
21
  */
20
22
  export function Shot({ shot }: { shot: WriteupShot }) {
21
23
  return (
@@ -15,8 +15,9 @@ export interface WriteupPerson {
15
15
  /**
16
16
  * The sign off a printed artifact closes on: the script line, then the person
17
17
  * lockup, the photo beside the name, the role, and the address
18
- * (Brand/voice.md, Pitch Surfaces 9). The writeup and the monthly page both
19
- * wear it, so its styles live in the base sheet chain
18
+ * (Brand/voice.md, Pitch Surfaces 9). The documents in @atelic-action/artifact
19
+ * (the writeup, the read, the monthly page, the note) all wear it, so its
20
+ * styles live in the base sheet chain
20
21
  * (styles/artifact.css); its container sets the type size and the color.
21
22
  */
22
23
  export function SignOff({ signOff, person }: { signOff: string; person: WriteupPerson }) {
@@ -1,3 +1,9 @@
1
+ /**
2
+ * The artifact entry: the pieces a document is built from and the site
3
+ * around it. The composed documents (the writeup, the read, the monthly
4
+ * page, the note, and the sealed document) live in @atelic-action/artifact,
5
+ * which builds them from what this entry exports.
6
+ */
1
7
  export { newTabProps } from "../lib/newTabProps";
2
8
  export { AppErrorBoundary } from "./AppErrorBoundary";
3
9
  export { ArtifactShell, type ArtifactShellProps } from "./ArtifactShell";
@@ -9,11 +15,16 @@ export type {
9
15
  GateConfig,
10
16
  } from "./artifactConfig";
11
17
  export { ErrorFallback } from "./ErrorFallback";
12
- export { Gate, type GatePayload, type GatePerson, type GateProps, normalizeEmail } from "./Gate";
13
- export { GradeChip, gradeLabel } from "./GradeChip";
14
- export * from "./Note";
18
+ export { GradeChip, gradeLabel, type WriteupGrade } from "./GradeChip";
19
+ export {
20
+ ArtifactAnchor,
21
+ type ArtifactLink,
22
+ type ArtifactParagraph,
23
+ isEmpty,
24
+ Paragraph,
25
+ Runs,
26
+ } from "./Paragraph";
15
27
  export { buildPageHead, canonicalUrl, type PageMeta } from "./pageHead";
16
- export * from "./Report";
17
28
  export {
18
29
  mintToken,
19
30
  openReader,
@@ -22,5 +33,5 @@ export {
22
33
  sealReader,
23
34
  whoIsReading,
24
35
  } from "./readers";
25
- export { SignOff } from "./SignOff";
26
- export * from "./Writeup";
36
+ export { Shot, type WriteupShot } from "./Shot";
37
+ export { SignOff, type WriteupPerson } from "./SignOff";