@hraness/design-kit 0.35.0 → 0.35.2

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/dist/stylex.css CHANGED
@@ -712,10 +712,6 @@
712
712
  padding-block: .25rem;
713
713
  }
714
714
 
715
- .x1267ecw {
716
- padding-block: .35rem;
717
- }
718
-
719
715
  .x1bilvtl {
720
716
  padding-block: .375rem;
721
717
  }
@@ -728,6 +724,10 @@
728
724
  padding-block: .5rem;
729
725
  }
730
726
 
727
+ .x8fiyw4 {
728
+ padding-block: .65rem;
729
+ }
730
+
731
731
  .xo0yzjp {
732
732
  padding-block: .75rem;
733
733
  }
@@ -816,10 +816,6 @@
816
816
  padding-inline: .625rem;
817
817
  }
818
818
 
819
- .x1f6otoe {
820
- padding-inline: .65rem;
821
- }
822
-
823
819
  .x1ryrjj2 {
824
820
  padding-inline: .75rem;
825
821
  }
@@ -1283,10 +1279,6 @@
1283
1279
  background-color: color-mix(in srgb,var(--foreground) 8%,transparent);
1284
1280
  }
1285
1281
 
1286
- .xf7cb96 {
1287
- background-color: color-mix(in srgb,var(--hraness-marketing-ink, var(--foreground, CanvasText)) 4%,var(--hraness-marketing-surface, var(--surface, Canvas)));
1288
- }
1289
-
1290
1282
  .xy1scu2 {
1291
1283
  background-color: color-mix(in srgb,var(--hraness-marketing-ink, var(--foreground, CanvasText)) 5%,var(--hraness-marketing-background, var(--background, Canvas)));
1292
1284
  }
@@ -2587,6 +2579,10 @@
2587
2579
  grid-template-columns: repeat(auto-fit,minmax(min(100%,var(--_hraness-marketing-grid-track, 18rem)),1fr));
2588
2580
  }
2589
2581
 
2582
+ .xe92ajh {
2583
+ grid-template-columns: repeat(var(--_hraness-agent-target-wide-columns, var(--_hraness-agent-target-columns, 1)),minmax(0,1fr));
2584
+ }
2585
+
2590
2586
  .x3g07o8 {
2591
2587
  grid-template-columns: repeat(var(--hraness-marketing-fact-columns, 4),minmax(0,1fr));
2592
2588
  }
@@ -3769,10 +3765,6 @@
3769
3765
  background-color: color-mix(in srgb,var(--hraness-marketing-ink, var(--foreground, CanvasText)) 6%,transparent);
3770
3766
  }
3771
3767
 
3772
- .x3oli7v:hover {
3773
- background-color: color-mix(in srgb,var(--hraness-marketing-ink, var(--foreground, CanvasText)) 9%,var(--hraness-marketing-surface, var(--surface, Canvas)));
3774
- }
3775
-
3776
3768
  .xjoriyk:hover {
3777
3769
  background-color: color-mix(in srgb,var(--hraness-marketing-related-tone, var(--hraness-marketing-ink)) 6%,transparent);
3778
3770
  }
@@ -4376,6 +4368,10 @@
4376
4368
  border-right-width: 2px;
4377
4369
  }
4378
4370
 
4371
+ .x1h3am7x {
4372
+ border-top-color: color-mix(in srgb,var(--hraness-marketing-ink, var(--foreground, CanvasText)) 12%,transparent);
4373
+ }
4374
+
4379
4375
  .x1v8p93f {
4380
4376
  border-top-color: #0000;
4381
4377
  }
@@ -4392,6 +4388,10 @@
4392
4388
  border-top-style: solid;
4393
4389
  }
4394
4390
 
4391
+ .x178xt8z {
4392
+ border-top-width: 1px;
4393
+ }
4394
+
4395
4395
  .xamhcws {
4396
4396
  border-top-width: 2px;
4397
4397
  }
@@ -4404,10 +4404,6 @@
4404
4404
  bottom: 0;
4405
4405
  }
4406
4406
 
4407
- .x1at03k5 {
4408
- height: .95rem;
4409
- }
4410
-
4411
4407
  .xqtp20y {
4412
4408
  height: 0;
4413
4409
  }
@@ -4540,10 +4536,6 @@
4540
4536
  min-height: 250px;
4541
4537
  }
4542
4538
 
4543
- .x1in32aq {
4544
- min-height: 2rem;
4545
- }
4546
-
4547
4539
  .x13gjtz1 {
4548
4540
  min-height: 3.5rem;
4549
4541
  }
@@ -4628,10 +4620,6 @@
4628
4620
  top: var(--hraness-design-procedural-ripple-y);
4629
4621
  }
4630
4622
 
4631
- .x2tfi8i {
4632
- width: .95rem;
4633
- }
4634
-
4635
4623
  .xnalus7 {
4636
4624
  width: 0;
4637
4625
  }
@@ -5863,6 +5851,18 @@
5863
5851
  }
5864
5852
  }
5865
5853
 
5854
+ @container hraness-agent-setup (width >= 20rem) {
5855
+ .x6fuqh4.x6fuqh4 {
5856
+ --_hraness-agent-target-columns: 2;
5857
+ }
5858
+ }
5859
+
5860
+ @container hraness-agent-setup (width >= 34rem) {
5861
+ .x1ii36b5.x1ii36b5 {
5862
+ --_hraness-agent-target-wide-columns: 3;
5863
+ }
5864
+ }
5865
+
5866
5866
  @container hraness-platform-install (width <= 30rem) {
5867
5867
  .x1rp0mr4.x1rp0mr4 {
5868
5868
  --_hraness-platform-install-narrow-tab-font-size: .8125rem;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hraness/design-kit",
3
- "version": "0.35.0",
3
+ "version": "0.35.2",
4
4
  "description": "React components and CSS for Hraness apps and product sites: application shells, marketing sections, charts, themes, effects, and syntax highlighting. Built on @hraness/ui.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -300,6 +300,8 @@
300
300
  "src/agent-setup.ts",
301
301
  "src/react/agent-setup-prompt.tsx",
302
302
  "src/react/agent-setup-prompt.stylex.ts",
303
+ "src/react/setup-copy-button.tsx",
304
+ "src/react/setup-copy-button.stylex.ts",
303
305
  "AGENT_SETUP.md",
304
306
  "src/react/marketing-comparison.tsx",
305
307
  "src/react/marketing-comparison.stylex.ts",
@@ -6,7 +6,7 @@
6
6
  "name": "@hraness/design-kit",
7
7
  "path": ".",
8
8
  "visibility": "public",
9
- "version": "0.35.0"
9
+ "version": "0.35.2"
10
10
  }
11
11
  ],
12
12
  "contract": "hraness.portfolio-inventory/v1",
@@ -83,6 +83,8 @@ export interface ArticleHtmlInput {
83
83
  readonly id?: string;
84
84
  readonly provenance: ArticleProvenanceRecord | null;
85
85
  readonly published: ArticleIsoDate;
86
+ /** Show publication and update dates. Defaults to true; dates remain required and validated when hidden. */
87
+ readonly showDates?: boolean;
86
88
  readonly toc?: readonly ArticleTocItem[];
87
89
  readonly tocLabel?: string;
88
90
  readonly updated?: ArticleIsoDate;
@@ -91,7 +93,7 @@ export interface ArticleHtmlInput {
91
93
  /** The static equivalent of `MarketingArticle`. */
92
94
  export function renderArticleHtml(input: ArticleHtmlInput): string {
93
95
  const headingId = input.headingId ?? "article-title";
94
- const { published, updated } = input;
96
+ const { published, updated, showDates = true } = input;
95
97
  assertArticleDates(updated === undefined ? { published } : { published, updated });
96
98
  const toc = input.toc ?? [];
97
99
  for (const item of toc) {
@@ -99,16 +101,16 @@ export function renderArticleHtml(input: ArticleHtmlInput): string {
99
101
  }
100
102
  const tocId = `${headingId}-contents`;
101
103
  const meta = [
102
- input.author === undefined ? "" : renderArticleBylineHtml(input.author) + SEPARATOR,
103
- dateHtml("Published", published),
104
- updated === undefined ? "" : SEPARATOR + dateHtml("Updated", updated),
105
- ].join("");
104
+ input.author === undefined ? "" : renderArticleBylineHtml(input.author),
105
+ showDates ? dateHtml("Published", published) : "",
106
+ showDates && updated !== undefined ? dateHtml("Updated", updated) : "",
107
+ ].filter(Boolean).join(SEPARATOR);
106
108
  const header = [
107
109
  '<header class="plain-publication__article-header">',
108
110
  present(input.eyebrow) ? `<p class="plain-publication__eyebrow">${escapeArticleHtml(input.eyebrow)}</p>` : "",
109
111
  `<h1 id="${escapeArticleHtml(headingId)}">${escapeArticleHtml(input.heading)}</h1>`,
110
112
  present(input.dek) ? `<p class="plain-publication__article-dek">${escapeArticleHtml(input.dek)}</p>` : "",
111
- `<p class="plain-publication__article-meta">${meta}</p>`,
113
+ meta === "" ? "" : `<p class="plain-publication__article-meta">${meta}</p>`,
112
114
  input.provenance === null ? "" : renderArticleProvenanceHtml(input.provenance),
113
115
  "</header>",
114
116
  ].join("");
@@ -135,21 +137,35 @@ export function renderArticleHtml(input: ArticleHtmlInput): string {
135
137
  export function renderArticleSourcesHtml({
136
138
  heading = ARTICLE_SOURCES_HEADING,
137
139
  headingId = "article-sources",
140
+ showDates = true,
138
141
  sources,
139
- }: Readonly<{ heading?: string; headingId?: string; sources: readonly ArticleSourceItem[] }>): string {
142
+ }: Readonly<{
143
+ heading?: string;
144
+ headingId?: string;
145
+ /** Show check dates. Defaults to true; checkedOn remains required and validated when hidden. */
146
+ showDates?: boolean;
147
+ sources: readonly ArticleSourceItem[];
148
+ }>): string {
140
149
  if (sources.length === 0) return "";
141
- for (const source of sources) assertArticleHref(source.href);
150
+ for (const source of sources) {
151
+ assertArticleHref(source.href);
152
+ formatArticleDate(source.checkedOn);
153
+ }
142
154
  return [
143
155
  `<section aria-labelledby="${escapeArticleHtml(headingId)}" class="plain-publication__sources">`,
144
156
  `<h2 id="${escapeArticleHtml(headingId)}">${escapeArticleHtml(heading)}</h2>`,
145
157
  "<ol>",
146
- ...sources.map((source) => [
147
- `<li><a href="${escapeArticleHtml(source.href)}">${escapeArticleHtml(source.title)}</a>`,
148
- "<span>",
149
- present(source.publisher) ? escapeArticleHtml(source.publisher) + SEPARATOR : "",
150
- dateHtml("Checked", source.checkedOn),
151
- "</span></li>",
152
- ].join("")),
158
+ ...sources.map((source) => {
159
+ const meta = [
160
+ present(source.publisher) ? escapeArticleHtml(source.publisher) : "",
161
+ showDates ? dateHtml("Checked", source.checkedOn) : "",
162
+ ].filter(Boolean).join(SEPARATOR);
163
+ return [
164
+ `<li><a href="${escapeArticleHtml(source.href)}">${escapeArticleHtml(source.title)}</a>`,
165
+ meta === "" ? "" : `<span>${meta}</span>`,
166
+ "</li>",
167
+ ].join("");
168
+ }),
153
169
  "</ol></section>",
154
170
  ].join("");
155
171
  }
@@ -228,6 +244,7 @@ export function renderArticleIndexHtml({
228
244
  headingLevel = 2,
229
245
  id,
230
246
  items,
247
+ showDates = true,
231
248
  summary,
232
249
  }: Readonly<{
233
250
  className?: string;
@@ -236,6 +253,8 @@ export function renderArticleIndexHtml({
236
253
  headingLevel?: 1 | 2 | 3 | 4 | 5;
237
254
  id?: string;
238
255
  items: readonly ArticleIndexItem[];
256
+ /** Show publication and update dates. Defaults to true; item dates remain required and validated when hidden. */
257
+ showDates?: boolean;
239
258
  summary?: string;
240
259
  }>): string {
241
260
  if (![1, 2, 3, 4, 5].includes(headingLevel)) throw new RangeError("Article index heading level must be 1 to 5.");
@@ -259,10 +278,13 @@ export function renderArticleIndexHtml({
259
278
  present(item.eyebrow) ? `<p class="plain-publication__entry-label">${escapeArticleHtml(item.eyebrow)}</p>` : "",
260
279
  `<${entry} class="plain-publication__entry-title"><a href="${escapeArticleHtml(item.href)}">${escapeArticleHtml(item.title)}</a></${entry}>`,
261
280
  `<p class="plain-publication__entry-dek">${escapeArticleHtml(item.dek)}</p>`,
262
- '<p class="plain-publication__entry-meta">',
263
- dateHtml("Published", item.published),
264
- item.updated === undefined ? "" : SEPARATOR + dateHtml("Updated", item.updated),
265
- "</p></article>",
281
+ showDates ? [
282
+ '<p class="plain-publication__entry-meta">',
283
+ dateHtml("Published", item.published),
284
+ item.updated === undefined ? "" : SEPARATOR + dateHtml("Updated", item.updated),
285
+ "</p>",
286
+ ].join("") : "",
287
+ "</article>",
266
288
  ].join("")),
267
289
  "</div></section>",
268
290
  ].join("");
@@ -89,39 +89,19 @@ export const agentSetupStyles = stylex.create({
89
89
  ":hover": { color: ink },
90
90
  "::marker": { content: '""' },
91
91
  },
92
- copy: {
93
- alignItems: "center",
94
- appearance: "none",
95
- backgroundColor: { default: background, ":hover": surface, [forced]: "ButtonFace" },
96
- borderColor: { default: line, [forced]: "ButtonText" },
97
- borderRadius: "0.5rem",
98
- borderStyle: "solid",
99
- borderWidth: "1px",
100
- boxSizing: "border-box",
101
- color: { default: ink, [forced]: "ButtonText" },
102
- cursor: "pointer",
103
- display: { default: "inline-flex", [noScript]: "none" },
104
- flexShrink: 0,
105
- fontFamily: "inherit",
106
- fontSize: "0.8125rem",
107
- gap: "0.4rem",
108
- justifyContent: "center",
109
- lineHeight: 1,
110
- margin: 0,
111
- minBlockSize: "2.75rem",
112
- outline: { default: "none", ":focus-visible": `2px solid ${focus}` },
113
- outlineOffset: "2px",
114
- padding: "0.65rem 0.8rem",
115
- },
116
92
  copyOverlay: {
117
93
  insetBlockEnd: "0.5rem",
118
94
  insetInlineEnd: "0.5rem",
119
95
  position: "absolute",
120
96
  },
121
- copyIcon: { blockSize: "1rem", flexShrink: 0, inlineSize: "1rem" },
122
97
  targets: { display: "grid", gap: "0.6rem", minInlineSize: 0 },
123
98
  targetsLabel: { color: { default: muted, [forced]: "CanvasText" }, fontSize: "0.8125rem", fontWeight: 500, margin: 0 },
124
99
  targetList: { display: "grid", gap: "0.45rem", gridTemplateColumns: "repeat(auto-fit, minmax(min(100%, 10rem), 1fr))", listStyle: "none", margin: 0, padding: 0 },
100
+ targetGrid: {
101
+ gridTemplateColumns: "repeat(var(--_hraness-agent-target-wide-columns, var(--_hraness-agent-target-columns, 1)), minmax(0, 1fr))",
102
+ "--_hraness-agent-target-columns": { default: null, "@container hraness-agent-setup (min-width: 20rem)": "2" },
103
+ "--_hraness-agent-target-wide-columns": { default: null, "@container hraness-agent-setup (min-width: 34rem)": "3" },
104
+ },
125
105
  target: {
126
106
  alignItems: "center",
127
107
  backgroundColor: { default: surface, ":hover": background, [forced]: "ButtonFace" },
@@ -5,6 +5,7 @@ import { useCallback, useEffect, useId, useRef, useState, type KeyboardEvent, ty
5
5
  import type { ProviderMarkDescriptor } from "../provider-marks.js";
6
6
  import { agentSetupClassName as cx } from "./agent-setup-prompt.stylex.js";
7
7
  import { ProviderMark } from "./provider-mark.js";
8
+ import { SetupCopyButton } from "./setup-copy-button.js";
8
9
  import { SyntaxCode } from "./syntax-code.js";
9
10
 
10
11
  export interface AgentSetupTarget {
@@ -20,6 +21,8 @@ export interface AgentSetupPromptProps {
20
21
  readonly prompt: string;
21
22
  readonly label?: string;
22
23
  readonly targets?: readonly AgentSetupTarget[];
24
+ /** Keep provider actions below the prompt, or place them beside it in wide containers. */
25
+ readonly targetsPlacement?: "responsive" | "below";
23
26
  readonly className?: string;
24
27
  /** Runs once after a successful copy; callback failures do not change copy feedback. */
25
28
  readonly onCopied?: () => void;
@@ -186,21 +189,8 @@ function useCopy(text: string, subject: string, fallback: () => HTMLElement | nu
186
189
  return { state, message, copy };
187
190
  }
188
191
 
189
- function CopyGlyph({ copied }: Readonly<{ copied: boolean }>) {
190
- return (
191
- <svg aria-hidden="true" className={cx(["copyIcon"])} fill="none" focusable="false" stroke="currentColor" strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} viewBox="0 0 24 24">
192
- {copied ? <path d="M5 12.5l4.5 4.5L19 7.5" /> : <><rect height="12" rx="2" width="12" x="8" y="8" /><path d="M16 8V6a2 2 0 0 0-2-2H6a2 2 0 0 0-2 2v8a2 2 0 0 0 2 2h2" /></>}
193
- </svg>
194
- );
195
- }
196
-
197
192
  function CopyButton({ copy, overlay = false, state, subject }: Readonly<{ copy: () => Promise<boolean>; overlay?: boolean; state: CopyState; subject: string }>) {
198
- return (
199
- <button aria-busy={state === "copying" || undefined} aria-label={`${state === "copied" ? "Copied" : "Copy"} ${subject}`} className={cx(["copy", overlay && "copyOverlay"])} data-copy-state={state} disabled={state === "copying"} onClick={() => { void copy(); }} type="button">
200
- <CopyGlyph copied={state === "copied"} />
201
- <span>{state === "copied" ? "Copied" : state === "copying" ? "Copying" : state === "failed" ? "Copy failed" : "Copy"}</span>
202
- </button>
203
- );
193
+ return <SetupCopyButton className={cx([overlay && "copyOverlay"], "hraness-agent-setup__copy")} iconClassName="hraness-agent-setup__copyIcon" onCopy={() => { void copy(); }} state={state} subject={subject} />;
204
194
  }
205
195
 
206
196
  function CopyStatus({ children }: Readonly<{ children: ReactNode }>) {
@@ -231,10 +221,11 @@ function reserveTargetTab(owner: Window | null): Window | null {
231
221
  }
232
222
 
233
223
  /** A quiet prompt preview with the complete source behind a native disclosure. */
234
- export function AgentSetupPrompt({ prompt, label = "Agent setup", targets = [], className, onCopied }: AgentSetupPromptProps) {
224
+ export function AgentSetupPrompt({ prompt, label = "Agent setup", targets = [], targetsPlacement = "responsive", className, onCopied }: AgentSetupPromptProps) {
235
225
  assertText(prompt, "Agent setup prompt");
236
226
  assertText(label, "Agent setup label");
237
227
  assertEntries(targets, "Agent setup target");
228
+ if (targetsPlacement !== "responsive" && targetsPlacement !== "below") throw new RangeError("Unsupported agent setup targets placement.");
238
229
  for (const target of targets) {
239
230
  assertHref(target.href);
240
231
  if (target.mode !== undefined && target.mode !== "prefill" && target.mode !== "copy-and-open") throw new RangeError("Unsupported agent setup target mode.");
@@ -313,8 +304,8 @@ export function AgentSetupPrompt({ prompt, label = "Agent setup", targets = [],
313
304
  }, []);
314
305
 
315
306
  return (
316
- <section aria-label={label} className={cx(["root"], className)} data-hraness-agent-setup-prompt="">
317
- <div className={cx(["layout", targets.length > 0 && "withTargets"])}>
307
+ <section aria-label={label} className={cx(["root"], className)} data-hraness-agent-setup-prompt="" data-targets-placement={targetsPlacement}>
308
+ <div className={cx(["layout", targets.length > 0 && targetsPlacement === "responsive" && "withTargets"])}>
318
309
  <div className={cx(["frame"])} data-copy-state={state}>
319
310
  <div className={cx(["preview"])} hidden={expanded}>
320
311
  <pre aria-label={`${label} preview`} className={cx(["pre", "previewText"])}>{prompt}</pre>
@@ -330,7 +321,7 @@ export function AgentSetupPrompt({ prompt, label = "Agent setup", targets = [],
330
321
  {targets.length === 0 ? null : (
331
322
  <aside aria-labelledby={`${id}-targets`} className={cx(["targets"])}>
332
323
  <p className={cx(["targetsLabel"])} id={`${id}-targets`}>Open in</p>
333
- <ul className={cx(["targetList"])}>
324
+ <ul className={cx(["targetList", targetsPlacement === "below" && "targetGrid"])}>
334
325
  {targets.map((target) => (
335
326
  <li key={target.id}>
336
327
  <a aria-busy={target.mode === "copy-and-open" && state === "copying" || undefined} aria-disabled={target.mode === "copy-and-open" && state === "copying" || undefined} aria-label={target.mode === "copy-and-open" ? `Copy prompt and open ${target.label}` : undefined} className={cx(["target"])} data-agent-target={target.id} data-agent-target-mode={target.mode ?? "prefill"} href={target.href} onAuxClick={target.mode === "copy-and-open" ? (event) => { copyAndOpen(event, target); } : undefined} onClick={target.mode === "copy-and-open" ? (event) => { copyAndOpen(event, target); } : undefined} rel="noopener noreferrer" target="_blank">
@@ -93,6 +93,8 @@ export interface MarketingArticleProps {
93
93
  /** Required so every article decides its drafting note. Pass null only for writing with no AI involvement and no review record. */
94
94
  readonly provenance: ArticleProvenanceRecord | null;
95
95
  readonly published: ArticleIsoDate;
96
+ /** Show publication and update dates. Defaults to true; dates remain required and validated when hidden. */
97
+ readonly showDates?: boolean;
96
98
  readonly toc?: readonly ArticleTocItem[];
97
99
  readonly tocLabel?: string;
98
100
  readonly updated?: ArticleIsoDate;
@@ -116,6 +118,7 @@ export function MarketingArticle({
116
118
  id,
117
119
  provenance,
118
120
  published,
121
+ showDates = true,
119
122
  toc,
120
123
  tocLabel = ARTICLE_TOC_LABEL,
121
124
  updated,
@@ -138,11 +141,13 @@ export function MarketingArticle({
138
141
  {eyebrow === undefined || eyebrow === "" ? null : <p className="plain-publication__eyebrow">{eyebrow}</p>}
139
142
  <h1 id={headingId}>{heading}</h1>
140
143
  {dek === undefined || dek === "" ? null : <p className="plain-publication__article-dek">{dek}</p>}
141
- <p className="plain-publication__article-meta">
142
- {author === undefined ? null : <><ArticleByline author={author} /><Separator /></>}
143
- <ArticleDate label="Published" value={published} />
144
- {updated === undefined ? null : <><Separator /><ArticleDate label="Updated" value={updated} /></>}
145
- </p>
144
+ {author === undefined && !showDates ? null : (
145
+ <p className="plain-publication__article-meta">
146
+ {author === undefined ? null : <><ArticleByline author={author} />{showDates ? <Separator /> : null}</>}
147
+ {showDates ? <ArticleDate label="Published" value={published} /> : null}
148
+ {!showDates || updated === undefined ? null : <><Separator /><ArticleDate label="Updated" value={updated} /></>}
149
+ </p>
150
+ )}
146
151
  {provenance === null ? null : <ArticleProvenance provenance={provenance} />}
147
152
  </header>
148
153
  <div className="plain-publication__article-layout">
@@ -165,14 +170,20 @@ export function MarketingArticle({
165
170
  export function ArticleSources({
166
171
  heading = ARTICLE_SOURCES_HEADING,
167
172
  headingId = "article-sources",
173
+ showDates = true,
168
174
  sources,
169
175
  }: Readonly<{
170
176
  heading?: string;
171
177
  headingId?: string;
178
+ /** Show check dates. Defaults to true; checkedOn remains required and validated when hidden. */
179
+ showDates?: boolean;
172
180
  sources: readonly ArticleSourceItem[];
173
181
  }>) {
174
182
  if (sources.length === 0) return null;
175
- for (const source of sources) assertArticleHref(source.href);
183
+ for (const source of sources) {
184
+ assertArticleHref(source.href);
185
+ formatArticleDate(source.checkedOn);
186
+ }
176
187
  return (
177
188
  <section aria-labelledby={headingId} className="plain-publication__sources">
178
189
  <h2 id={headingId}>{heading}</h2>
@@ -180,10 +191,12 @@ export function ArticleSources({
180
191
  {sources.map((source) => (
181
192
  <li key={`${source.href}-${source.title}`}>
182
193
  <a href={source.href}>{source.title}</a>
183
- <span>
184
- {source.publisher === undefined || source.publisher === "" ? null : <>{source.publisher}<Separator /></>}
185
- <ArticleDate label="Checked" value={source.checkedOn} />
186
- </span>
194
+ {!showDates && (source.publisher === undefined || source.publisher === "") ? null : (
195
+ <span>
196
+ {source.publisher === undefined || source.publisher === "" ? null : <>{source.publisher}{showDates ? <Separator /> : null}</>}
197
+ {showDates ? <ArticleDate label="Checked" value={source.checkedOn} /> : null}
198
+ </span>
199
+ )}
187
200
  </li>
188
201
  ))}
189
202
  </ol>
@@ -253,6 +266,7 @@ export function ArticleIndex({
253
266
  headingLevel = 2,
254
267
  id,
255
268
  items,
269
+ showDates = true,
256
270
  summary,
257
271
  }: Readonly<{
258
272
  className?: string;
@@ -261,6 +275,8 @@ export function ArticleIndex({
261
275
  headingLevel?: 1 | 2 | 3 | 4 | 5;
262
276
  id?: string;
263
277
  items: readonly ArticleIndexItem[];
278
+ /** Show publication and update dates. Defaults to true; item dates remain required and validated when hidden. */
279
+ showDates?: boolean;
264
280
  summary?: string;
265
281
  }>) {
266
282
  if (![1, 2, 3, 4, 5].includes(headingLevel)) throw new RangeError("Article index heading level must be 1 to 5.");
@@ -290,10 +306,12 @@ export function ArticleIndex({
290
306
  {item.eyebrow === undefined || item.eyebrow === "" ? null : <p className="plain-publication__entry-label">{item.eyebrow}</p>}
291
307
  <EntryTag className="plain-publication__entry-title"><a href={item.href}>{item.title}</a></EntryTag>
292
308
  <p className="plain-publication__entry-dek">{item.dek}</p>
293
- <p className="plain-publication__entry-meta">
294
- <ArticleDate label="Published" value={item.published} />
295
- {item.updated === undefined ? null : <><Separator /><ArticleDate label="Updated" value={item.updated} /></>}
296
- </p>
309
+ {showDates ? (
310
+ <p className="plain-publication__entry-meta">
311
+ <ArticleDate label="Published" value={item.published} />
312
+ {item.updated === undefined ? null : <><Separator /><ArticleDate label="Updated" value={item.updated} /></>}
313
+ </p>
314
+ ) : null}
297
315
  </article>
298
316
  ))}
299
317
  </div>
@@ -401,6 +401,7 @@ export function DesignSystemGallery({
401
401
  <MarketingSection heading="Ask your agent to set it up." headingId="gallery-agent-setup" headingLevel={3} label="Agent setup">
402
402
  <AgentSetupPrompt
403
403
  label="Relay agent setup"
404
+ targetsPlacement="below"
404
405
  prompt={"Install Relay from https://relay.example/install.sh.\nRead the project instructions before making changes.\nRun relay init in this workspace and create a job named sample.\nRun the job, then show me its log."}
405
406
  targets={[
406
407
  { href: "https://claude.ai/new", id: "claude", label: "Claude", mark: "claudecode", mode: "copy-and-open" },
@@ -677,6 +678,7 @@ export function DesignSystemGallery({
677
678
  after={(
678
679
  <ArticleSources
679
680
  headingId="gallery-article-sources"
681
+ showDates={false}
680
682
  sources={[
681
683
  { checkedOn: "2026-09-20", href: "#articles", publisher: "Relay", title: "Relay 2.4 release notes" },
682
684
  ]}
@@ -692,6 +694,7 @@ export function DesignSystemGallery({
692
694
  review: { reviewer: "an independent AI editorial review", reviewerType: "ai" },
693
695
  }}
694
696
  published="2026-09-10"
697
+ showDates={false}
695
698
  toc={[
696
699
  { href: "#gallery-article-problem", label: "The problem" },
697
700
  { href: "#gallery-article-approach", label: "The approach" },
@@ -221,40 +221,6 @@ export const platformInstallStyles = stylex.create({
221
221
  fontSize: "inherit",
222
222
  padding: 0,
223
223
  },
224
- copy: {
225
- alignItems: "center",
226
- appearance: "none",
227
- backgroundColor: {
228
- default: `color-mix(in srgb, ${ink} 4%, ${surface})`,
229
- ":hover": `color-mix(in srgb, ${ink} 9%, ${surface})`,
230
- [forced]: "ButtonFace",
231
- },
232
- borderColor: { default: line, [forced]: "ButtonText" },
233
- borderRadius: "0.5rem",
234
- borderStyle: "solid",
235
- borderWidth: "1px",
236
- boxSizing: "border-box",
237
- color: { default: ink, [forced]: "ButtonText" },
238
- cursor: "pointer",
239
- display: { default: "inline-flex", [noScript]: "none" },
240
- flexShrink: 0,
241
- fontFamily: textFont,
242
- fontSize: "0.8125rem",
243
- fontWeight: 500,
244
- gap: "0.35rem",
245
- lineHeight: 1,
246
- margin: 0,
247
- minBlockSize: { default: "2rem", "@media (pointer: coarse)": "2.75rem" },
248
- outline: { default: "none", ":focus-visible": `2px solid ${focusRing}` },
249
- outlineOffset: "1px",
250
- paddingBlock: "0.35rem",
251
- paddingInline: "0.65rem",
252
- },
253
- copyIcon: {
254
- blockSize: "0.95rem",
255
- flexShrink: 0,
256
- inlineSize: "0.95rem",
257
- },
258
224
  alternatives: {
259
225
  display: "grid",
260
226
  gap: "0.6rem",
@@ -269,6 +235,13 @@ export const platformInstallStyles = stylex.create({
269
235
  lineHeight: 1.5,
270
236
  margin: 0,
271
237
  },
238
+ commandNote: {
239
+ borderBlockStartColor: { default: line, [forced]: "CanvasText" },
240
+ borderBlockStartStyle: "solid",
241
+ borderBlockStartWidth: "1px",
242
+ paddingBlock: "0.65rem",
243
+ paddingInline: "0.95rem",
244
+ },
272
245
  unavailable: {
273
246
  borderColor: { default: line, [forced]: "CanvasText" },
274
247
  borderRadius: "0.75rem",
@@ -359,8 +332,6 @@ const hooks: Partial<Record<Part, string>> = {
359
332
  code: "hraness-platform-install__code",
360
333
  command: "hraness-platform-install__command",
361
334
  commandBar: "hraness-platform-install__command-bar",
362
- copy: "hraness-platform-install__copy",
363
- copyIcon: "hraness-platform-install__copy-icon",
364
335
  icon: "hraness-platform-icon",
365
336
  markSymbols: "hraness-platform-install__marks",
366
337
  note: "hraness-platform-install__note",