@eduardoalvarez/arrecife 0.8.0 → 0.9.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/CHANGELOG.md CHANGED
@@ -1,5 +1,13 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.9.0](https://github.com/Proskynete/arrecife/compare/v0.8.0...v0.9.0) (2026-09-08)
4
+
5
+
6
+ ### 🚀 Novedades
7
+
8
+ * **chart:** the axes get their own tick formatters ([25e9311](https://github.com/Proskynete/arrecife/commit/25e93118775b9ce0c6091d3a98cd9067f7ddc8cb))
9
+ * **components:** the signature prints the site's domain, not the identity's ([6d9839d](https://github.com/Proskynete/arrecife/commit/6d9839dbf43b8820431a78c9100dac607e6e1b8e))
10
+
3
11
  ## [0.8.0](https://github.com/Proskynete/arrecife/compare/v0.7.0...v0.8.0) (2026-09-06)
4
12
 
5
13
 
@@ -299,6 +299,7 @@ var rows = (data) => data;
299
299
  var color = (series2, index) => series2.color ?? seriesColor(index);
300
300
  var MARGIN = { left: 0, right: 12, top: 8, bottom: 0 };
301
301
  var AXIS = { tickLine: false, axisLine: false };
302
+ var tickOf = (format) => format ? { tickFormatter: format } : {};
302
303
  var legendOf = (series2, legend) => legend ?? series2.length > 1 ? /* @__PURE__ */ jsxRuntime.jsx(ChartLegend, { content: /* @__PURE__ */ jsxRuntime.jsx(ChartLegendContent, {}) }, "legend") : null;
303
304
  var tooltipOf = (formatter) => /* @__PURE__ */ jsxRuntime.jsx(
304
305
  ChartTooltip,
@@ -313,6 +314,8 @@ function AreaChart({
313
314
  series: series2,
314
315
  xKey,
315
316
  formatter,
317
+ xTickFormatter,
318
+ yTickFormatter,
316
319
  legend,
317
320
  stacked = false,
318
321
  ...container
@@ -324,8 +327,8 @@ function AreaChart({
324
327
  /* @__PURE__ */ jsxRuntime.jsx("stop", { offset: "95%", stopColor: color(s, i), stopOpacity: 0.05 })
325
328
  ] }, s.key)) }),
326
329
  /* @__PURE__ */ jsxRuntime.jsx(recharts.CartesianGrid, { vertical: false }),
327
- /* @__PURE__ */ jsxRuntime.jsx(recharts.XAxis, { dataKey: xKey, ...AXIS, tickMargin: 8 }),
328
- /* @__PURE__ */ jsxRuntime.jsx(recharts.YAxis, { ...AXIS, width: 40 }),
330
+ /* @__PURE__ */ jsxRuntime.jsx(recharts.XAxis, { dataKey: xKey, ...AXIS, tickMargin: 8, ...tickOf(xTickFormatter) }),
331
+ /* @__PURE__ */ jsxRuntime.jsx(recharts.YAxis, { ...AXIS, width: 40, ...tickOf(yTickFormatter) }),
329
332
  tooltipOf(formatter),
330
333
  legendOf(series2, legend),
331
334
  series2.map((s, i) => /* @__PURE__ */ jsxRuntime.jsx(
@@ -349,6 +352,8 @@ function BarChart({
349
352
  series: series2,
350
353
  xKey,
351
354
  formatter,
355
+ xTickFormatter,
356
+ yTickFormatter,
352
357
  legend,
353
358
  orientation = "vertical",
354
359
  stacked = false,
@@ -364,11 +369,30 @@ function BarChart({
364
369
  children: [
365
370
  /* @__PURE__ */ jsxRuntime.jsx(recharts.CartesianGrid, { vertical: lying, horizontal: !lying }),
366
371
  lying ? [
367
- /* @__PURE__ */ jsxRuntime.jsx(recharts.YAxis, { dataKey: xKey, type: "category", ...AXIS, width: 130 }, "category"),
368
- /* @__PURE__ */ jsxRuntime.jsx(recharts.XAxis, { type: "number", hide: true }, "value")
372
+ /* @__PURE__ */ jsxRuntime.jsx(
373
+ recharts.YAxis,
374
+ {
375
+ dataKey: xKey,
376
+ type: "category",
377
+ ...AXIS,
378
+ width: 130,
379
+ ...tickOf(xTickFormatter)
380
+ },
381
+ "category"
382
+ ),
383
+ /* @__PURE__ */ jsxRuntime.jsx(recharts.XAxis, { type: "number", hide: true, ...tickOf(yTickFormatter) }, "value")
369
384
  ] : [
370
- /* @__PURE__ */ jsxRuntime.jsx(recharts.XAxis, { dataKey: xKey, ...AXIS, tickMargin: 8 }, "category"),
371
- /* @__PURE__ */ jsxRuntime.jsx(recharts.YAxis, { ...AXIS, width: 40 }, "value")
385
+ /* @__PURE__ */ jsxRuntime.jsx(
386
+ recharts.XAxis,
387
+ {
388
+ dataKey: xKey,
389
+ ...AXIS,
390
+ tickMargin: 8,
391
+ ...tickOf(xTickFormatter)
392
+ },
393
+ "category"
394
+ ),
395
+ /* @__PURE__ */ jsxRuntime.jsx(recharts.YAxis, { ...AXIS, width: 40, ...tickOf(yTickFormatter) }, "value")
372
396
  ],
373
397
  tooltipOf(formatter),
374
398
  legendOf(series2, legend),
@@ -393,13 +417,15 @@ function LineChart({
393
417
  series: series2,
394
418
  xKey,
395
419
  formatter,
420
+ xTickFormatter,
421
+ yTickFormatter,
396
422
  legend,
397
423
  ...container
398
424
  }) {
399
425
  return /* @__PURE__ */ jsxRuntime.jsx(ChartContainer, { ...container, children: /* @__PURE__ */ jsxRuntime.jsxs(recharts.LineChart, { data: rows(data), margin: MARGIN, children: [
400
426
  /* @__PURE__ */ jsxRuntime.jsx(recharts.CartesianGrid, { vertical: false }),
401
- /* @__PURE__ */ jsxRuntime.jsx(recharts.XAxis, { dataKey: xKey, ...AXIS, tickMargin: 8 }),
402
- /* @__PURE__ */ jsxRuntime.jsx(recharts.YAxis, { ...AXIS, width: 40 }),
427
+ /* @__PURE__ */ jsxRuntime.jsx(recharts.XAxis, { dataKey: xKey, ...AXIS, tickMargin: 8, ...tickOf(xTickFormatter) }),
428
+ /* @__PURE__ */ jsxRuntime.jsx(recharts.YAxis, { ...AXIS, width: 40, ...tickOf(yTickFormatter) }),
403
429
  tooltipOf(formatter),
404
430
  legendOf(series2, legend),
405
431
  series2.map((s, i) => /* @__PURE__ */ jsxRuntime.jsx(
@@ -165,6 +165,28 @@ type SeriesChartProps = Omit<ChartContainerProps, 'children'> & {
165
165
  xKey: string;
166
166
  /** Formats the value in the tooltip. The library imposes no locale. */
167
167
  formatter?: ((value: unknown) => ReactNode) | undefined;
168
+ /**
169
+ * Formats the TICK on the category axis. Returns a string, because an axis
170
+ * tick is an SVG `<text>` and not a place a node can go.
171
+ *
172
+ * It is separate from `formatter` because the two answer different questions.
173
+ * The tooltip has room for «15 de julio de 2026»; a tick on a 30-day series
174
+ * has room for «07-15», and without this the axis prints the raw key —
175
+ * `2026-07-15` repeated thirty times, overlapping into a grey band.
176
+ *
177
+ * The library imposes no format: a date key can be a day, a month or a course
178
+ * name, and only the project knows which. See `docs/decisions/0.9.md` § 50.
179
+ */
180
+ xTickFormatter?: ((value: unknown) => string) | undefined;
181
+ /**
182
+ * Formats the tick on the VALUE axis — the currency symbol, the thousands
183
+ * separator, the percent sign.
184
+ *
185
+ * Same reason as `xTickFormatter` and the same shape. `formatter` reaches the
186
+ * tooltip only, so without this a revenue chart reads «1200» on the axis and
187
+ * «$1200» in the tooltip, and the axis is the one you read while comparing.
188
+ */
189
+ yTickFormatter?: ((value: unknown) => string) | undefined;
168
190
  /**
169
191
  * Shows the legend. It defaults to «only when there is more than one series»:
170
192
  * a legend naming the one line already named by the chart's own heading is a
@@ -195,7 +217,7 @@ type AreaChartProps = SeriesChartProps & {
195
217
  /** Adds the series up instead of overlaying them. */
196
218
  stacked?: boolean | undefined;
197
219
  };
198
- declare function AreaChart({ data, series, xKey, formatter, legend, stacked, ...container }: AreaChartProps): react.JSX.Element;
220
+ declare function AreaChart({ data, series, xKey, formatter, xTickFormatter, yTickFormatter, legend, stacked, ...container }: AreaChartProps): react.JSX.Element;
199
221
  /**
200
222
  * Bars, upright or lying down.
201
223
  *
@@ -219,7 +241,7 @@ type BarChartProps = SeriesChartProps & {
219
241
  /** Stacks the series instead of putting them side by side. */
220
242
  stacked?: boolean | undefined;
221
243
  };
222
- declare function BarChart({ data, series, xKey, formatter, legend, orientation, stacked, ...container }: BarChartProps): react.JSX.Element;
244
+ declare function BarChart({ data, series, xKey, formatter, xTickFormatter, yTickFormatter, legend, orientation, stacked, ...container }: BarChartProps): react.JSX.Element;
223
245
  /**
224
246
  * Lines, for comparing series against each other.
225
247
  *
@@ -232,6 +254,6 @@ declare function BarChart({ data, series, xKey, formatter, legend, orientation,
232
254
  * where it means «this one».
233
255
  */
234
256
  type LineChartProps = SeriesChartProps;
235
- declare function LineChart({ data, series, xKey, formatter, legend, ...container }: LineChartProps): react.JSX.Element;
257
+ declare function LineChart({ data, series, xKey, formatter, xTickFormatter, yTickFormatter, legend, ...container }: LineChartProps): react.JSX.Element;
236
258
 
237
259
  export { AreaChart, type AreaChartProps, BarChart, type BarChartProps, ChartContainer, type ChartContainerProps, type ChartDatum, ChartLegend, ChartLegendContent, type ChartLegendContentProps, type ChartPayloadItem, type ChartSeries, ChartTooltip, ChartTooltipContent, type ChartTooltipContentProps, LineChart, type LineChartProps, SERIES_COLORS, type SeriesChartProps, seriesColor };
@@ -165,6 +165,28 @@ type SeriesChartProps = Omit<ChartContainerProps, 'children'> & {
165
165
  xKey: string;
166
166
  /** Formats the value in the tooltip. The library imposes no locale. */
167
167
  formatter?: ((value: unknown) => ReactNode) | undefined;
168
+ /**
169
+ * Formats the TICK on the category axis. Returns a string, because an axis
170
+ * tick is an SVG `<text>` and not a place a node can go.
171
+ *
172
+ * It is separate from `formatter` because the two answer different questions.
173
+ * The tooltip has room for «15 de julio de 2026»; a tick on a 30-day series
174
+ * has room for «07-15», and without this the axis prints the raw key —
175
+ * `2026-07-15` repeated thirty times, overlapping into a grey band.
176
+ *
177
+ * The library imposes no format: a date key can be a day, a month or a course
178
+ * name, and only the project knows which. See `docs/decisions/0.9.md` § 50.
179
+ */
180
+ xTickFormatter?: ((value: unknown) => string) | undefined;
181
+ /**
182
+ * Formats the tick on the VALUE axis — the currency symbol, the thousands
183
+ * separator, the percent sign.
184
+ *
185
+ * Same reason as `xTickFormatter` and the same shape. `formatter` reaches the
186
+ * tooltip only, so without this a revenue chart reads «1200» on the axis and
187
+ * «$1200» in the tooltip, and the axis is the one you read while comparing.
188
+ */
189
+ yTickFormatter?: ((value: unknown) => string) | undefined;
168
190
  /**
169
191
  * Shows the legend. It defaults to «only when there is more than one series»:
170
192
  * a legend naming the one line already named by the chart's own heading is a
@@ -195,7 +217,7 @@ type AreaChartProps = SeriesChartProps & {
195
217
  /** Adds the series up instead of overlaying them. */
196
218
  stacked?: boolean | undefined;
197
219
  };
198
- declare function AreaChart({ data, series, xKey, formatter, legend, stacked, ...container }: AreaChartProps): react.JSX.Element;
220
+ declare function AreaChart({ data, series, xKey, formatter, xTickFormatter, yTickFormatter, legend, stacked, ...container }: AreaChartProps): react.JSX.Element;
199
221
  /**
200
222
  * Bars, upright or lying down.
201
223
  *
@@ -219,7 +241,7 @@ type BarChartProps = SeriesChartProps & {
219
241
  /** Stacks the series instead of putting them side by side. */
220
242
  stacked?: boolean | undefined;
221
243
  };
222
- declare function BarChart({ data, series, xKey, formatter, legend, orientation, stacked, ...container }: BarChartProps): react.JSX.Element;
244
+ declare function BarChart({ data, series, xKey, formatter, xTickFormatter, yTickFormatter, legend, orientation, stacked, ...container }: BarChartProps): react.JSX.Element;
223
245
  /**
224
246
  * Lines, for comparing series against each other.
225
247
  *
@@ -232,6 +254,6 @@ declare function BarChart({ data, series, xKey, formatter, legend, orientation,
232
254
  * where it means «this one».
233
255
  */
234
256
  type LineChartProps = SeriesChartProps;
235
- declare function LineChart({ data, series, xKey, formatter, legend, ...container }: LineChartProps): react.JSX.Element;
257
+ declare function LineChart({ data, series, xKey, formatter, xTickFormatter, yTickFormatter, legend, ...container }: LineChartProps): react.JSX.Element;
236
258
 
237
259
  export { AreaChart, type AreaChartProps, BarChart, type BarChartProps, ChartContainer, type ChartContainerProps, type ChartDatum, ChartLegend, ChartLegendContent, type ChartLegendContentProps, type ChartPayloadItem, type ChartSeries, ChartTooltip, ChartTooltipContent, type ChartTooltipContentProps, LineChart, type LineChartProps, SERIES_COLORS, type SeriesChartProps, seriesColor };
@@ -106,6 +106,7 @@ var rows = (data) => data;
106
106
  var color = (series, index) => series.color ?? seriesColor(index);
107
107
  var MARGIN = { left: 0, right: 12, top: 8, bottom: 0 };
108
108
  var AXIS = { tickLine: false, axisLine: false };
109
+ var tickOf = (format) => format ? { tickFormatter: format } : {};
109
110
  var legendOf = (series, legend) => legend ?? series.length > 1 ? /* @__PURE__ */ jsx(ChartLegend, { content: /* @__PURE__ */ jsx(ChartLegendContent, {}) }, "legend") : null;
110
111
  var tooltipOf = (formatter) => /* @__PURE__ */ jsx(
111
112
  ChartTooltip,
@@ -120,6 +121,8 @@ function AreaChart({
120
121
  series,
121
122
  xKey,
122
123
  formatter,
124
+ xTickFormatter,
125
+ yTickFormatter,
123
126
  legend,
124
127
  stacked = false,
125
128
  ...container
@@ -131,8 +134,8 @@ function AreaChart({
131
134
  /* @__PURE__ */ jsx("stop", { offset: "95%", stopColor: color(s, i), stopOpacity: 0.05 })
132
135
  ] }, s.key)) }),
133
136
  /* @__PURE__ */ jsx(CartesianGrid, { vertical: false }),
134
- /* @__PURE__ */ jsx(XAxis, { dataKey: xKey, ...AXIS, tickMargin: 8 }),
135
- /* @__PURE__ */ jsx(YAxis, { ...AXIS, width: 40 }),
137
+ /* @__PURE__ */ jsx(XAxis, { dataKey: xKey, ...AXIS, tickMargin: 8, ...tickOf(xTickFormatter) }),
138
+ /* @__PURE__ */ jsx(YAxis, { ...AXIS, width: 40, ...tickOf(yTickFormatter) }),
136
139
  tooltipOf(formatter),
137
140
  legendOf(series, legend),
138
141
  series.map((s, i) => /* @__PURE__ */ jsx(
@@ -156,6 +159,8 @@ function BarChart({
156
159
  series,
157
160
  xKey,
158
161
  formatter,
162
+ xTickFormatter,
163
+ yTickFormatter,
159
164
  legend,
160
165
  orientation = "vertical",
161
166
  stacked = false,
@@ -171,11 +176,30 @@ function BarChart({
171
176
  children: [
172
177
  /* @__PURE__ */ jsx(CartesianGrid, { vertical: lying, horizontal: !lying }),
173
178
  lying ? [
174
- /* @__PURE__ */ jsx(YAxis, { dataKey: xKey, type: "category", ...AXIS, width: 130 }, "category"),
175
- /* @__PURE__ */ jsx(XAxis, { type: "number", hide: true }, "value")
179
+ /* @__PURE__ */ jsx(
180
+ YAxis,
181
+ {
182
+ dataKey: xKey,
183
+ type: "category",
184
+ ...AXIS,
185
+ width: 130,
186
+ ...tickOf(xTickFormatter)
187
+ },
188
+ "category"
189
+ ),
190
+ /* @__PURE__ */ jsx(XAxis, { type: "number", hide: true, ...tickOf(yTickFormatter) }, "value")
176
191
  ] : [
177
- /* @__PURE__ */ jsx(XAxis, { dataKey: xKey, ...AXIS, tickMargin: 8 }, "category"),
178
- /* @__PURE__ */ jsx(YAxis, { ...AXIS, width: 40 }, "value")
192
+ /* @__PURE__ */ jsx(
193
+ XAxis,
194
+ {
195
+ dataKey: xKey,
196
+ ...AXIS,
197
+ tickMargin: 8,
198
+ ...tickOf(xTickFormatter)
199
+ },
200
+ "category"
201
+ ),
202
+ /* @__PURE__ */ jsx(YAxis, { ...AXIS, width: 40, ...tickOf(yTickFormatter) }, "value")
179
203
  ],
180
204
  tooltipOf(formatter),
181
205
  legendOf(series, legend),
@@ -200,13 +224,15 @@ function LineChart({
200
224
  series,
201
225
  xKey,
202
226
  formatter,
227
+ xTickFormatter,
228
+ yTickFormatter,
203
229
  legend,
204
230
  ...container
205
231
  }) {
206
232
  return /* @__PURE__ */ jsx(ChartContainer, { ...container, children: /* @__PURE__ */ jsxs(LineChart$1, { data: rows(data), margin: MARGIN, children: [
207
233
  /* @__PURE__ */ jsx(CartesianGrid, { vertical: false }),
208
- /* @__PURE__ */ jsx(XAxis, { dataKey: xKey, ...AXIS, tickMargin: 8 }),
209
- /* @__PURE__ */ jsx(YAxis, { ...AXIS, width: 40 }),
234
+ /* @__PURE__ */ jsx(XAxis, { dataKey: xKey, ...AXIS, tickMargin: 8, ...tickOf(xTickFormatter) }),
235
+ /* @__PURE__ */ jsx(YAxis, { ...AXIS, width: 40, ...tickOf(yTickFormatter) }),
210
236
  tooltipOf(formatter),
211
237
  legendOf(series, legend),
212
238
  series.map((s, i) => /* @__PURE__ */ jsx(
package/dist/index.cjs CHANGED
@@ -3255,6 +3255,7 @@ function SocialRow({ social }) {
3255
3255
  function Signature({
3256
3256
  year,
3257
3257
  href,
3258
+ domain: domainName = naming.domain,
3258
3259
  className
3259
3260
  }) {
3260
3261
  const domain = href ? /* @__PURE__ */ jsxRuntime.jsx(
@@ -3262,9 +3263,9 @@ function Signature({
3262
3263
  {
3263
3264
  href,
3264
3265
  className: "text-accent hover:text-text-primary transition-standard rounded-chip focus-ring cursor-pointer",
3265
- children: naming.domain
3266
+ children: domainName
3266
3267
  }
3267
- ) : naming.domain;
3268
+ ) : domainName;
3268
3269
  return /* @__PURE__ */ jsxRuntime.jsxs(Text, { variant: "meta", tone: "muted", as: "p", className: cn("shrink-0", className), children: [
3269
3270
  /* @__PURE__ */ jsxRuntime.jsxs("span", { "aria-hidden": "true", className: "text-accent", children: [
3270
3271
  "$",
@@ -3307,6 +3308,7 @@ function Footer({
3307
3308
  brand: brand2,
3308
3309
  year = (/* @__PURE__ */ new Date()).getFullYear(),
3309
3310
  signatureHref,
3311
+ domain,
3310
3312
  className,
3311
3313
  ...rest
3312
3314
  }) {
@@ -3346,7 +3348,7 @@ function Footer({
3346
3348
  /* @__PURE__ */ jsxRuntime.jsx("ul", { className: "gap-step-xs flex flex-col", children: column.links.map((columnLink) => /* @__PURE__ */ jsxRuntime.jsx("li", { children: link(columnLink.href, columnLink.external, columnLink.label) }, columnLink.href)) })
3347
3349
  ] }, column.title)) })
3348
3350
  ] }),
3349
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "border-hairline mt-step-xl pt-step-lg border-t", children: /* @__PURE__ */ jsxRuntime.jsx(Signature, { year, href: signatureHref, className: "text-center sm:text-right" }) })
3351
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "border-hairline mt-step-xl pt-step-lg border-t", children: /* @__PURE__ */ jsxRuntime.jsx(Signature, { year, href: signatureHref, domain, className: "text-center sm:text-right" }) })
3350
3352
  ] });
3351
3353
  }
3352
3354
  const rows = [
@@ -3357,7 +3359,7 @@ function Footer({
3357
3359
  return /* @__PURE__ */ jsxRuntime.jsxs(Shell, { className, contentClassName: "gap-step-lg", ...rest, children: [
3358
3360
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "gap-step-md flex flex-wrap items-center", children: [
3359
3361
  first ?? null,
3360
- /* @__PURE__ */ jsxRuntime.jsx(Signature, { year, href: signatureHref, className: "ml-auto" })
3362
+ /* @__PURE__ */ jsxRuntime.jsx(Signature, { year, href: signatureHref, domain, className: "ml-auto" })
3361
3363
  ] }),
3362
3364
  others
3363
3365
  ] });
package/dist/index.d.cts CHANGED
@@ -950,9 +950,10 @@ declare function EventCalendar({ events, onCreateEvent, onUpdateEvent, onDeleteE
950
950
  /**
951
951
  * The footer, and the site's CLI signature: `$ cd ~/eduardoalvarez.dev/2026`.
952
952
  *
953
- * The domain comes from `naming.domain` and not from a hand-written string, for
953
+ * The domain defaults to `naming.domain` and not to a hand-written string, for
954
954
  * the same reason as the wordmark: if it changes, it changes in all five
955
- * projects at once.
955
+ * projects at once. A project that lives on its OWN domain passes `domain` —
956
+ * see `docs/decisions/0.9.md` § 49.
956
957
  *
957
958
  * The social links are icons with NO visible text, so `aria-label` is not an
958
959
  * improvement: it is the only thing that makes them legible. Which is why it is
@@ -1038,6 +1039,21 @@ type FooterBase = Omit<ComponentPropsWithoutRef<'footer'>, 'children'> & {
1038
1039
  * signature is text, which is what it has always been.
1039
1040
  */
1040
1041
  signatureHref?: string | undefined;
1042
+ /**
1043
+ * The domain the signature prints, defaulting to the identity's own.
1044
+ *
1045
+ * It exists because the default is right for the sites that ARE
1046
+ * `eduardoalvarez.dev` and wrong for the ones that are not: `cursos` lives on
1047
+ * `cursos.eduardoalvarez.dev` and had been printing it since before this
1048
+ * component existed. Adopting the footer would have made a site sign with its
1049
+ * parent's domain — a fact, not a style, and one nothing in the type could
1050
+ * have warned about.
1051
+ *
1052
+ * It is a domain and not a whole signature: the `$`, the `cd ~/` and the year
1053
+ * are the identity's and stay the component's. See `docs/decisions/0.9.md`
1054
+ * § 49.
1055
+ */
1056
+ domain?: string | undefined;
1041
1057
  };
1042
1058
  type FooterProps = FooterBase & ({
1043
1059
  /** The shape the library has always had: stacked rows and the signature at the top right. */
@@ -1073,7 +1089,7 @@ type FooterProps = FooterBase & ({
1073
1089
  children: ReactNode;
1074
1090
  }) => ReactNode) | undefined;
1075
1091
  });
1076
- declare function Footer({ variant, columns, description, action, linkAsChild, social, brand, year, signatureHref, className, ...rest }: FooterProps): react.JSX.Element;
1092
+ declare function Footer({ variant, columns, description, action, linkAsChild, social, brand, year, signatureHref, domain, className, ...rest }: FooterProps): react.JSX.Element;
1077
1093
 
1078
1094
  /**
1079
1095
  * ONE per site. It is the only piece in the system that is spent like the
package/dist/index.d.ts CHANGED
@@ -950,9 +950,10 @@ declare function EventCalendar({ events, onCreateEvent, onUpdateEvent, onDeleteE
950
950
  /**
951
951
  * The footer, and the site's CLI signature: `$ cd ~/eduardoalvarez.dev/2026`.
952
952
  *
953
- * The domain comes from `naming.domain` and not from a hand-written string, for
953
+ * The domain defaults to `naming.domain` and not to a hand-written string, for
954
954
  * the same reason as the wordmark: if it changes, it changes in all five
955
- * projects at once.
955
+ * projects at once. A project that lives on its OWN domain passes `domain` —
956
+ * see `docs/decisions/0.9.md` § 49.
956
957
  *
957
958
  * The social links are icons with NO visible text, so `aria-label` is not an
958
959
  * improvement: it is the only thing that makes them legible. Which is why it is
@@ -1038,6 +1039,21 @@ type FooterBase = Omit<ComponentPropsWithoutRef<'footer'>, 'children'> & {
1038
1039
  * signature is text, which is what it has always been.
1039
1040
  */
1040
1041
  signatureHref?: string | undefined;
1042
+ /**
1043
+ * The domain the signature prints, defaulting to the identity's own.
1044
+ *
1045
+ * It exists because the default is right for the sites that ARE
1046
+ * `eduardoalvarez.dev` and wrong for the ones that are not: `cursos` lives on
1047
+ * `cursos.eduardoalvarez.dev` and had been printing it since before this
1048
+ * component existed. Adopting the footer would have made a site sign with its
1049
+ * parent's domain — a fact, not a style, and one nothing in the type could
1050
+ * have warned about.
1051
+ *
1052
+ * It is a domain and not a whole signature: the `$`, the `cd ~/` and the year
1053
+ * are the identity's and stay the component's. See `docs/decisions/0.9.md`
1054
+ * § 49.
1055
+ */
1056
+ domain?: string | undefined;
1041
1057
  };
1042
1058
  type FooterProps = FooterBase & ({
1043
1059
  /** The shape the library has always had: stacked rows and the signature at the top right. */
@@ -1073,7 +1089,7 @@ type FooterProps = FooterBase & ({
1073
1089
  children: ReactNode;
1074
1090
  }) => ReactNode) | undefined;
1075
1091
  });
1076
- declare function Footer({ variant, columns, description, action, linkAsChild, social, brand, year, signatureHref, className, ...rest }: FooterProps): react.JSX.Element;
1092
+ declare function Footer({ variant, columns, description, action, linkAsChild, social, brand, year, signatureHref, domain, className, ...rest }: FooterProps): react.JSX.Element;
1077
1093
 
1078
1094
  /**
1079
1095
  * ONE per site. It is the only piece in the system that is spent like the
package/dist/index.js CHANGED
@@ -2510,6 +2510,7 @@ function SocialRow({ social }) {
2510
2510
  function Signature({
2511
2511
  year,
2512
2512
  href,
2513
+ domain: domainName = naming.domain,
2513
2514
  className
2514
2515
  }) {
2515
2516
  const domain = href ? /* @__PURE__ */ jsx(
@@ -2517,9 +2518,9 @@ function Signature({
2517
2518
  {
2518
2519
  href,
2519
2520
  className: "text-accent hover:text-text-primary transition-standard rounded-chip focus-ring cursor-pointer",
2520
- children: naming.domain
2521
+ children: domainName
2521
2522
  }
2522
- ) : naming.domain;
2523
+ ) : domainName;
2523
2524
  return /* @__PURE__ */ jsxs(Text, { variant: "meta", tone: "muted", as: "p", className: cn("shrink-0", className), children: [
2524
2525
  /* @__PURE__ */ jsxs("span", { "aria-hidden": "true", className: "text-accent", children: [
2525
2526
  "$",
@@ -2562,6 +2563,7 @@ function Footer({
2562
2563
  brand: brand2,
2563
2564
  year = (/* @__PURE__ */ new Date()).getFullYear(),
2564
2565
  signatureHref,
2566
+ domain,
2565
2567
  className,
2566
2568
  ...rest
2567
2569
  }) {
@@ -2601,7 +2603,7 @@ function Footer({
2601
2603
  /* @__PURE__ */ jsx("ul", { className: "gap-step-xs flex flex-col", children: column.links.map((columnLink) => /* @__PURE__ */ jsx("li", { children: link(columnLink.href, columnLink.external, columnLink.label) }, columnLink.href)) })
2602
2604
  ] }, column.title)) })
2603
2605
  ] }),
2604
- /* @__PURE__ */ jsx("div", { className: "border-hairline mt-step-xl pt-step-lg border-t", children: /* @__PURE__ */ jsx(Signature, { year, href: signatureHref, className: "text-center sm:text-right" }) })
2606
+ /* @__PURE__ */ jsx("div", { className: "border-hairline mt-step-xl pt-step-lg border-t", children: /* @__PURE__ */ jsx(Signature, { year, href: signatureHref, domain, className: "text-center sm:text-right" }) })
2605
2607
  ] });
2606
2608
  }
2607
2609
  const rows = [
@@ -2612,7 +2614,7 @@ function Footer({
2612
2614
  return /* @__PURE__ */ jsxs(Shell, { className, contentClassName: "gap-step-lg", ...rest, children: [
2613
2615
  /* @__PURE__ */ jsxs("div", { className: "gap-step-md flex flex-wrap items-center", children: [
2614
2616
  first ?? null,
2615
- /* @__PURE__ */ jsx(Signature, { year, href: signatureHref, className: "ml-auto" })
2617
+ /* @__PURE__ */ jsx(Signature, { year, href: signatureHref, domain, className: "ml-auto" })
2616
2618
  ] }),
2617
2619
  others
2618
2620
  ] });
package/llms.txt CHANGED
@@ -1448,6 +1448,7 @@ Source: `src/components/footer/index.tsx`
1448
1448
  | `brand` | `ReactNode` | | | The brand row: the fin and the wordmark, at the very top. |
1449
1449
  | `columns` | `readonly FooterColumn[]` | | | The link columns. Mandatory: without them `full` is the default form with extra steps. |
1450
1450
  | `description` | `ReactNode` | | | One line under the brand, saying what the site is. |
1451
+ | `domain` | `string` | | | The domain the signature prints, defaulting to the identity's own. |
1451
1452
  | `linkAsChild` | `(props: { href: string; children: ReactNode; }) => ReactNode` | | | Renders the column links through the child, to plug in the framework's `Link`. It receives each `href` in the Slot's `props`. |
1452
1453
  | `signatureHref` | `string` | | | Makes the domain inside the signature a link, keeping the `$`, the path and the prompt's mark as text. |
1453
1454
  | `social` | `readonly SocialLink[]` | | | |
@@ -1863,6 +1864,8 @@ A series over time, with the fill fading out underneath it.
1863
1864
  | `stacked` | `boolean \| undefined` | | `false` | Adds the series up instead of overlaying them. |
1864
1865
  | `summary` | `ReactNode` | | | What the chart says, in words. It goes in a visually hidden `figcaption`. |
1865
1866
  | `xKey` | `string` | yes | | The key on the category axis: the day, the month, the course. |
1867
+ | `xTickFormatter` | `(value: unknown) => string` | | | Formats the TICK on the category axis. Returns a string, because an axis tick is an SVG `<text>` and not a place a node can go. |
1868
+ | `yTickFormatter` | `(value: unknown) => string` | | | Formats the tick on the VALUE axis — the currency symbol, the thousands separator, the percent sign. |
1866
1869
 
1867
1870
  **BarChart**
1868
1871
  Bars, upright or lying down.
@@ -1881,6 +1884,8 @@ Bars, upright or lying down.
1881
1884
  | `stacked` | `boolean \| undefined` | | `false` | Stacks the series instead of putting them side by side. |
1882
1885
  | `summary` | `ReactNode` | | | What the chart says, in words. It goes in a visually hidden `figcaption`. |
1883
1886
  | `xKey` | `string` | yes | | The key on the category axis: the day, the month, the course. |
1887
+ | `xTickFormatter` | `(value: unknown) => string` | | | Formats the TICK on the category axis. Returns a string, because an axis tick is an SVG `<text>` and not a place a node can go. |
1888
+ | `yTickFormatter` | `(value: unknown) => string` | | | Formats the tick on the VALUE axis — the currency symbol, the thousands separator, the percent sign. |
1884
1889
 
1885
1890
  **LineChart**
1886
1891
  Lines, for comparing series against each other.
@@ -1897,6 +1902,8 @@ Lines, for comparing series against each other.
1897
1902
  | `series` | `readonly ChartSeries[]` | yes | | |
1898
1903
  | `summary` | `ReactNode` | | | What the chart says, in words. It goes in a visually hidden `figcaption`. |
1899
1904
  | `xKey` | `string` | yes | | The key on the category axis: the day, the month, the course. |
1905
+ | `xTickFormatter` | `(value: unknown) => string` | | | Formats the TICK on the category axis. Returns a string, because an axis tick is an SVG `<text>` and not a place a node can go. |
1906
+ | `yTickFormatter` | `(value: unknown) => string` | | | Formats the tick on the VALUE axis — the currency symbol, the thousands separator, the percent sign. |
1900
1907
 
1901
1908
  ## Exports that are not components
1902
1909
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@eduardoalvarez/arrecife",
3
- "version": "0.8.0",
3
+ "version": "0.9.0",
4
4
  "description": "The component library of Eduardo Álvarez’s visual identity",
5
5
  "license": "MIT",
6
6
  "author": "Eduardo Esteban Álvarez Castañeda <soy@eduardoalvarez.dev>",