@grimoire-rs/indexer 0.5.0 → 0.5.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.
@@ -0,0 +1,74 @@
1
+ ---
2
+ // SPDX-License-Identifier: Apache-2.0
3
+ // Copyright 2026 The Grimoire Authors
4
+
5
+ // A highlighted code block wearing the site's own copy affordance, and
6
+ // optionally a link out to VS Code.
7
+ //
8
+ // It exists for the same reason `CommandBar.astro` does: an index's own page
9
+ // under `theme/pages/` should be able to draw what the rest of the site draws
10
+ // rather than a `<pre>` that looks almost like it. A setup guide showing a
11
+ // config snippet, a publish guide showing a workflow — both want this, and
12
+ // neither wants to restate the Shiki theme pair or the copy markup.
13
+ //
14
+ // ---
15
+ // import CodeBlock from "@grim/components/CodeBlock.astro";
16
+ // import { vscodeUrl } from "@grim/lib/catalog";
17
+ // import { data } from "@grim/lib/data";
18
+ // ---
19
+ // <CodeBlock code="grim add acme/code-review" name="add command"
20
+ // vscodeHref={vscodeUrl(data.config.vscodeExtension, "acme/code-review")}
21
+ // vscodeLabel="Open in VS Code" />
22
+ //
23
+ // The copy button itself is NOT rendered here. `Base.astro` injects one into
24
+ // every code block on the page — it has to, because a block inside rendered
25
+ // markdown has no markup to hang one on — and it fills the `.code-actions`
26
+ // slot below when it finds it. One button, one clipboard path, one toast, and
27
+ // no second copy of the markup to drift.
28
+ import { Code } from "astro:components";
29
+ import { mdiMicrosoftVisualStudioCode } from "@mdi/js";
30
+ import { BrandMark } from "./BrandMark.tsx";
31
+ import { SHIKI_THEMES } from "../lib/code";
32
+
33
+ interface Props {
34
+ /** The snippet, verbatim. What the copy button copies. */
35
+ code: string;
36
+ /** Shiki language id. `sh` covers the commands this site mostly shows. */
37
+ lang?: string;
38
+ /** What the copy toast calls this, e.g. `"add command"`. */
39
+ name?: string;
40
+ /**
41
+ * Any URL for the trailing button — a `vscode://` deep link from
42
+ * `@grim/lib/catalog`'s builders, or one written by hand. Null or omitted
43
+ * draws no button, which is what an index with `vscodeExtension: null` gets
44
+ * from those builders.
45
+ */
46
+ vscodeHref?: string | null;
47
+ /** Accessible name for that button. */
48
+ vscodeLabel?: string;
49
+ }
50
+
51
+ const {
52
+ code,
53
+ lang = "sh",
54
+ name,
55
+ vscodeHref = null,
56
+ vscodeLabel = "Open in VS Code",
57
+ } = Astro.props;
58
+ ---
59
+
60
+ <div class="code-block" data-slot="code-block" data-copy-name={name}>
61
+ <Code code={code} lang={lang} themes={SHIKI_THEMES} />
62
+ {/* Present even when empty: it is where `Base.astro` puts the copy button,
63
+ and a block that renders one before the script runs would flash a second
64
+ one after it. */}
65
+ <span class="code-actions">
66
+ {
67
+ vscodeHref && (
68
+ <a class="code-vscode" href={vscodeHref} title={vscodeLabel} aria-label={vscodeLabel}>
69
+ <BrandMark path={mdiMicrosoftVisualStudioCode} size={15} />
70
+ </a>
71
+ )
72
+ }
73
+ </span>
74
+ </div>
@@ -279,10 +279,25 @@ const { title, description, image = config.logo ?? config.favicon } = Astro.prop
279
279
  '<path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2"/></svg>';
280
280
 
281
281
  for (const pre of document.querySelectorAll("pre.astro-code")) {
282
- const wrapper = document.createElement("div");
283
- wrapper.className = "code-block";
284
- pre.replaceWith(wrapper);
285
- wrapper.append(pre);
282
+ // `CodeBlock.astro` already ships the wrapper and the action slot, so
283
+ // adopt them where they are there. Wrapping a second time would nest
284
+ // two positioning contexts and orphan whatever the component put in
285
+ // the slot.
286
+ const parent = pre.parentElement;
287
+ let wrapper = parent?.classList.contains("code-block") ? parent : null;
288
+ if (!wrapper) {
289
+ wrapper = document.createElement("div");
290
+ wrapper.className = "code-block";
291
+ pre.replaceWith(wrapper);
292
+ wrapper.append(pre);
293
+ }
294
+
295
+ let actions = wrapper.querySelector(".code-actions");
296
+ if (!actions) {
297
+ actions = document.createElement("span");
298
+ actions.className = "code-actions";
299
+ wrapper.append(actions);
300
+ }
286
301
 
287
302
  const btn = document.createElement("button");
288
303
  btn.type = "button";
@@ -292,9 +307,10 @@ const { title, description, image = config.logo ?? config.favicon } = Astro.prop
292
307
  // `textContent`, not `innerText`: the block is not rendered yet at
293
308
  // this point, and `innerText` would collapse its whitespace.
294
309
  btn.dataset.copy = pre.textContent ?? "";
295
- btn.dataset.copyName = "code block";
310
+ // A component-rendered block names itself; a markdown one cannot.
311
+ btn.dataset.copyName = wrapper.dataset.copyName || "code block";
296
312
  btn.innerHTML = CLIPBOARD_ICON + '<span class="sr-only" role="status"></span>';
297
- wrapper.append(btn);
313
+ actions.append(btn);
298
314
  }
299
315
 
300
316
  for (const btn of document.querySelectorAll("[data-copy]")) {
@@ -917,6 +933,20 @@ const { title, description, image = config.logo ?? config.favicon } = Astro.prop
917
933
  .chip.deprecated-toggle {
918
934
  flex-shrink: 0;
919
935
  }
936
+ /* Lifts every keyword at once, and is only in the markup while there
937
+ is one to lift — so it never competes with the rail for room in the
938
+ common case. Muted until hovered: it undoes work, and it should not
939
+ read as another facet to pick. */
940
+ .chip.kw-clear {
941
+ flex-shrink: 0;
942
+ gap: var(--grim-space-2);
943
+ color: var(--grim-color-muted);
944
+ }
945
+ .chip.kw-clear:hover,
946
+ .chip.kw-clear:focus-visible {
947
+ border-color: var(--grim-color-accent);
948
+ color: var(--grim-color-fg);
949
+ }
920
950
  .kw-menu {
921
951
  flex-shrink: 0;
922
952
  }
@@ -1047,51 +1077,61 @@ const { title, description, image = config.logo ?? config.favicon } = Astro.prop
1047
1077
  transition: border-color var(--grim-duration-base) ease;
1048
1078
  }
1049
1079
  }
1050
- /* The overflow menu. A `<details>` for the same reasons the platform
1051
- picker is one it opens, closes and takes Escape without a line of
1052
- script and the panel is the `.os-menu` popover's measurements so
1053
- the two read as the same object in two places. */
1080
+ /* The overflow menu. A popover, and it has to be one: `.filter-row`
1081
+ above is a scroll container, and an absolutely-positioned panel
1082
+ inside a scroll container is cropped to the row AND stretches the
1083
+ row's scroll extent which is where a menu that opened invisibly,
1084
+ with a stray horizontal and vertical scrollbar to show for it, came
1085
+ from. The top layer is outside every ancestor's `overflow`, and it
1086
+ brings light dismiss and Escape, which the `<details>` this replaced
1087
+ never had. Its seat is `placeKwMenu` in `Catalog.tsx`. */
1054
1088
  .kw-menu {
1055
- position: relative;
1056
1089
  display: flex;
1057
1090
  }
1058
1091
  /* The trigger wears `.chip`, so its box comes from there and cannot
1059
1092
  drift a pixel off the chips it sits beside. Only what a chip does
1060
1093
  not cover is stated here. */
1061
- .kw-menu > summary {
1094
+ .kw-menu > button {
1062
1095
  display: inline-flex;
1063
1096
  align-items: center;
1064
1097
  color: var(--grim-color-muted);
1065
- /* Both spellings: the default triangle is a marker in Firefox and
1066
- a `::-webkit-details-marker` pseudo-element in older WebKit. */
1067
- list-style: none;
1068
- }
1069
- .kw-menu > summary::-webkit-details-marker {
1070
- display: none;
1071
1098
  }
1072
- .kw-menu > summary:hover,
1073
- .kw-menu > summary:focus-visible,
1074
- .kw-menu[open] > summary {
1099
+ .kw-menu > button:hover,
1100
+ .kw-menu > button:focus-visible,
1101
+ .kw-menu > button[aria-expanded="true"] {
1075
1102
  border-color: var(--grim-color-accent);
1076
1103
  color: var(--grim-color-fg);
1077
1104
  }
1105
+ /* `inset` and `margin` undo the UA's centring of a popover, so the
1106
+ `left`/`top` the script writes are the whole position. `color` is
1107
+ restated because the UA sets `CanvasText` on `[popover]` directly,
1108
+ which beats what the panel would otherwise inherit. `display` is
1109
+ deliberately absent: the UA's `display: none` for a closed popover
1110
+ has to keep winning, so the flex box is declared on `:popover-open`
1111
+ alone — an unconditional `display` here would show the menu shut. */
1078
1112
  .kw-menu-panel {
1079
- position: absolute;
1080
- top: calc(100% + var(--grim-space-2));
1081
- left: 0;
1082
- z-index: 5;
1083
- display: flex;
1084
- flex-direction: column;
1085
- gap: var(--grim-space-3);
1113
+ position: fixed;
1114
+ inset: auto;
1115
+ margin: 0;
1086
1116
  /* The panel's own measurement: wide enough for the longest keyword
1087
1117
  a publisher is likely to write plus its count, and capped so a
1088
1118
  long vocabulary scrolls instead of running off the viewport. */
1089
1119
  width: 15rem;
1090
1120
  padding: var(--grim-space-3);
1091
1121
  background: var(--grim-color-card);
1122
+ color: var(--grim-color-fg);
1092
1123
  border: var(--grim-border-width) solid var(--grim-color-border);
1093
1124
  border-radius: var(--grim-radius-control);
1094
1125
  box-shadow: var(--grim-shadow-raised);
1126
+ /* The list inside is the thing that scrolls. Without this the UA's
1127
+ own `overflow: auto` on `[popover]` adds a second scrollbar once
1128
+ the script caps the panel's height. */
1129
+ overflow: hidden;
1130
+ }
1131
+ .kw-menu-panel:popover-open {
1132
+ display: flex;
1133
+ flex-direction: column;
1134
+ gap: var(--grim-space-3);
1095
1135
  }
1096
1136
  .kw-menu-search {
1097
1137
  width: 100%;
@@ -1115,6 +1155,11 @@ const { title, description, image = config.logo ?? config.favicon } = Astro.prop
1115
1155
  as a list rather than a page. */
1116
1156
  max-height: 15rem;
1117
1157
  overflow-y: auto;
1158
+ /* And shorter still where the panel's own cap is tighter — a
1159
+ flex item's automatic minimum size is its content, so without
1160
+ `min-height` the list refuses to shrink and overflows the panel
1161
+ the script just sized to the space below the trigger. */
1162
+ min-height: 0;
1118
1163
  }
1119
1164
  .kw-menu-item {
1120
1165
  display: flex;
@@ -1208,6 +1253,27 @@ const { title, description, image = config.logo ?? config.favicon } = Astro.prop
1208
1253
  grid-template-columns: repeat(auto-fill, minmax(19rem, 1fr));
1209
1254
  gap: var(--grim-space-6);
1210
1255
  }
1256
+ /* Off-screen cards skip layout and paint until they are scrolled to.
1257
+ A card is the expensive item on this page — a masked keyword strip,
1258
+ a clipped watermark in its own stacking context, a logo — and the
1259
+ grid and the table are separate component trees, so switching view
1260
+ repaints every one of them at once.
1261
+
1262
+ `auto` remembers each card's last real height, so the scrollbar
1263
+ stops jumping after the first pass; the `18rem` is only the guess
1264
+ before that. Cards stay focusable and findable — the engine renders
1265
+ one on focus or find-in-page.
1266
+
1267
+ ponytail: this bounds the PAINT, not the JS. Measured in jsdom (no
1268
+ layout at all) the Preact commit for one view switch is ~30ms at 100
1269
+ packages, ~90-170ms at 500 and ~180-350ms at 1000, because the two
1270
+ views share no element types and every card unmounts as every row
1271
+ mounts. Past a few hundred packages the answer is windowing the
1272
+ list, not another CSS property. */
1273
+ .grid > li {
1274
+ content-visibility: auto;
1275
+ contain-intrinsic-size: auto 18rem;
1276
+ }
1211
1277
  .card {
1212
1278
  display: flex;
1213
1279
  flex-direction: column;
@@ -1351,10 +1417,6 @@ const { title, description, image = config.logo ?? config.favicon } = Astro.prop
1351
1417
  height: 36px;
1352
1418
  border-radius: var(--grim-radius-inset);
1353
1419
  }
1354
- img.card-logo {
1355
- object-fit: contain;
1356
- background: var(--grim-color-chip-bg);
1357
- }
1358
1420
  .card-logo-fallback {
1359
1421
  display: flex;
1360
1422
  align-items: center;
@@ -1375,17 +1437,26 @@ const { title, description, image = config.logo ?? config.favicon } = Astro.prop
1375
1437
 
1376
1438
  loading placeholder, no slash — the resting state
1377
1439
  ready image faded in, placeholder faded out
1378
- broken placeholder with its slash, dashed edge
1440
+ broken placeholder with its slash
1379
1441
 
1380
1442
  Kept distinct from `.card-logo-fallback`: that is a package with
1381
1443
  no logo at all, which is normal, not a fault. */
1444
+ /* No background. A logo is already a designed mark on its own ground,
1445
+ and a tinted square behind it framed it twice — worst on the many
1446
+ logos that are themselves a rounded square, which then sat inside a
1447
+ slightly larger rounded square. What is left is a box with a size,
1448
+ so the three states still swap without shifting anything. Nothing is
1449
+ drawn in any of them: the placeholder and the broken glyph carry
1450
+ their own colour, and the slash is what separates the two — a
1451
+ dashed frame around the broken one read as a fault in the layout
1452
+ rather than in the image, which is not what it means. The state is
1453
+ still announced, through `role="img"` and a label on the slot. */
1382
1454
  .logo-slot {
1383
1455
  position: relative;
1384
1456
  display: flex;
1385
1457
  align-items: center;
1386
1458
  justify-content: center;
1387
1459
  overflow: hidden;
1388
- background: var(--grim-color-chip-bg);
1389
1460
  color: var(--grim-color-muted);
1390
1461
  }
1391
1462
  .logo-slot .logo-mark,
@@ -1418,9 +1489,6 @@ const { title, description, image = config.logo ?? config.favicon } = Astro.prop
1418
1489
  .logo-slot:not([data-state="broken"]) .mark-broken {
1419
1490
  display: none;
1420
1491
  }
1421
- .logo-slot[data-state="broken"] {
1422
- border: var(--grim-border-width) dashed var(--grim-color-border);
1423
- }
1424
1492
  @media (prefers-reduced-motion: reduce) {
1425
1493
  .logo-slot .logo-mark,
1426
1494
  .logo-slot img {
@@ -1524,10 +1592,7 @@ const { title, description, image = config.logo ?? config.favicon } = Astro.prop
1524
1592
  name beside it starts on one line down the whole list. The colour
1525
1593
  is set inline, from the kind's own token or the deprecation one. */
1526
1594
  .t-kind {
1527
- text-align: center;
1528
- }
1529
- .t-kind svg {
1530
- vertical-align: middle;
1595
+ justify-content: center;
1531
1596
  }
1532
1597
  /* The card's tile at a row's height. Geometry, not a scale step, and
1533
1598
  measured against the text line beside it rather than against the
@@ -1561,23 +1626,33 @@ const { title, description, image = config.logo ?? config.favicon } = Astro.prop
1561
1626
  wide cell — they are one reading, and the gap that separated them
1562
1627
  was the cell's width showing through. */
1563
1628
  .t-rating {
1564
- text-align: right;
1629
+ justify-content: flex-end;
1565
1630
  color: var(--grim-color-muted);
1566
1631
  }
1567
1632
  .t-rating svg {
1568
- vertical-align: middle;
1569
1633
  margin-left: var(--grim-space-2);
1570
- /* Optical, not rhythm: the glyph's ink sits high in its own box, so
1571
- the middle of the box reads above the digits beside it. A pixel
1572
- down puts the two on the same visual line. */
1573
- translate: 0 1px;
1634
+ }
1635
+ /* The two cells that carry a glyph centre it as a box, not on the
1636
+ text line. `vertical-align: middle` aligns to half the x-height,
1637
+ which sits about 2px under the line's true middle at this step —
1638
+ the eye reads that as the mark and the arrow sagging below the
1639
+ words beside them. Neither cell owes anything to the row's
1640
+ no-flex rule above: the kind is one mark and the rating is a
1641
+ fixed-width count plus its arrow, so there is no text here for
1642
+ `text-overflow` to elide. */
1643
+ .t-kind,
1644
+ .t-rating {
1645
+ display: flex;
1646
+ align-items: center;
1574
1647
  }
1575
1648
  /* The count carries the column's alignment, and the arrow after it
1576
1649
  inherits it: a fixed box, right-aligned, in tabular figures. Without
1577
1650
  the fixed box a three-digit count would push its own arrow right of
1578
1651
  the one above it. Geometry — it holds four digits at this step. */
1579
1652
  .t-votes {
1580
- display: inline-block;
1653
+ /* Never shrunk to its digits: the box is what holds the arrow
1654
+ after it in one place down the column. */
1655
+ flex: none;
1581
1656
  width: 2rem;
1582
1657
  text-align: right;
1583
1658
  font-variant-numeric: tabular-nums;
@@ -1912,12 +1987,21 @@ const { title, description, image = config.logo ?? config.favicon } = Astro.prop
1912
1987
  .astro-code span:not([style*="background"]) {
1913
1988
  background-color: transparent !important;
1914
1989
  }
1915
- /* Reveals on hover or keyboard focus, so it is not a permanent
1916
- smudge over the first line of every block. */
1917
- .code-copy {
1990
+ /* The corner the block's buttons sit in. It is a row rather than one
1991
+ positioned button because `CodeBlock.astro` can add a second one —
1992
+ a VS Code deep link — and the copy button `Base.astro` injects has
1993
+ to land beside it without either knowing the other's width. */
1994
+ .code-actions {
1918
1995
  position: absolute;
1919
1996
  top: 0.45rem;
1920
1997
  right: 0.45rem;
1998
+ display: flex;
1999
+ gap: var(--grim-space-2);
2000
+ }
2001
+ /* Reveals on hover or keyboard focus, so they are not a permanent
2002
+ smudge over the first line of every block. */
2003
+ .code-copy,
2004
+ .code-vscode {
1921
2005
  display: inline-flex;
1922
2006
  align-items: center;
1923
2007
  justify-content: center;
@@ -1928,6 +2012,7 @@ const { title, description, image = config.logo ?? config.favicon } = Astro.prop
1928
2012
  border-radius: var(--grim-radius-inset);
1929
2013
  background: var(--grim-color-card);
1930
2014
  color: var(--grim-color-muted);
2015
+ text-decoration: none;
1931
2016
  cursor: pointer;
1932
2017
  opacity: 0;
1933
2018
  transition:
@@ -1936,10 +2021,13 @@ const { title, description, image = config.logo ?? config.favicon } = Astro.prop
1936
2021
  border-color var(--grim-duration-fast) ease;
1937
2022
  }
1938
2023
  .code-block:hover .code-copy,
1939
- .code-copy:focus-visible {
2024
+ .code-block:hover .code-vscode,
2025
+ .code-copy:focus-visible,
2026
+ .code-vscode:focus-visible {
1940
2027
  opacity: 1;
1941
2028
  }
1942
- .code-copy:hover {
2029
+ .code-copy:hover,
2030
+ .code-vscode:hover {
1943
2031
  color: var(--grim-color-accent);
1944
2032
  border-color: var(--grim-color-accent);
1945
2033
  }
@@ -1949,7 +2037,8 @@ const { title, description, image = config.logo ?? config.favicon } = Astro.prop
1949
2037
  opacity: 1;
1950
2038
  }
1951
2039
  @media (prefers-reduced-motion: reduce) {
1952
- .code-copy {
2040
+ .code-copy,
2041
+ .code-vscode {
1953
2042
  transition: none;
1954
2043
  }
1955
2044
  }
@@ -4,8 +4,8 @@
4
4
  * Shiki is what Astro already ships — the same highlighter VS Code renders
5
5
  * with — so nothing here adds a library; this only stops the two consumers
6
6
  * drifting onto different themes. The markdown pipeline reads it through
7
- * `astro.config`'s `shikiConfig`, and the `<Code>` component on the package
8
- * page takes it as a prop.
7
+ * `astro.config`'s `shikiConfig`, and `CodeBlock.astro` what every
8
+ * hand-written snippet on the site goes through — passes it to `<Code>`.
9
9
  *
10
10
  * A *pair*, not one theme: with a single theme a block stays dark on a light
11
11
  * page. Given two, Shiki writes the light colour as an inline style and the
@@ -6,8 +6,8 @@
6
6
  * Shiki is what Astro already ships — the same highlighter VS Code renders
7
7
  * with — so nothing here adds a library; this only stops the two consumers
8
8
  * drifting onto different themes. The markdown pipeline reads it through
9
- * `astro.config`'s `shikiConfig`, and the `<Code>` component on the package
10
- * page takes it as a prop.
9
+ * `astro.config`'s `shikiConfig`, and `CodeBlock.astro` what every
10
+ * hand-written snippet on the site goes through — passes it to `<Code>`.
11
11
  *
12
12
  * A *pair*, not one theme: with a single theme a block stays dark on a light
13
13
  * page. Given two, Shiki writes the light colour as an inline style and the
@@ -7,8 +7,8 @@
7
7
  * Shiki is what Astro already ships — the same highlighter VS Code renders
8
8
  * with — so nothing here adds a library; this only stops the two consumers
9
9
  * drifting onto different themes. The markdown pipeline reads it through
10
- * `astro.config`'s `shikiConfig`, and the `<Code>` component on the package
11
- * page takes it as a prop.
10
+ * `astro.config`'s `shikiConfig`, and `CodeBlock.astro` what every
11
+ * hand-written snippet on the site goes through — passes it to `<Code>`.
12
12
  *
13
13
  * A *pair*, not one theme: with a single theme a block stays dark on a light
14
14
  * page. Given two, Shiki writes the light colour as an inline style and the
@@ -1,4 +1,5 @@
1
- import type { ResolvedSiteConfig } from "../../../config.js";
1
+ import type { RegistryHint, ResolvedSiteConfig } from "../../../config.js";
2
+ export type { RegistryHint };
2
3
  /**
3
4
  * One option in a command bar: what it is called, what it copies, and the
4
5
  * glyph that stands for it.
@@ -35,8 +36,13 @@ export declare function installChoices(config: ResolvedSiteConfig): Choice[];
35
36
  * the `helm repo add` ergonomic. `null` when `registry` is unconfigured,
36
37
  * which means the index has no public URL to hand out; the block is omitted
37
38
  * rather than guessed.
39
+ *
40
+ * `registry` defaults to the site's own, and is a parameter so a page can draw
41
+ * the same bar for a *different* index: a corporate setup guide that hands out
42
+ * its own index in the hero and the public one further down needs two bars
43
+ * differing in nothing but this argument.
38
44
  */
39
- export declare function registryAddCommand(config: ResolvedSiteConfig): string | null;
45
+ export declare function registryAddCommand(config: ResolvedSiteConfig, registry?: RegistryHint | null): string | null;
40
46
  /**
41
47
  * Scope choices for the registry-add bar. Empty when there is no command to
42
48
  * scope — the bar can still render for the sake of its VS Code segment.
@@ -49,7 +55,7 @@ export declare function registryAddCommand(config: ResolvedSiteConfig): string |
49
55
  * flag, and appended after a long `--index <url>` it fell off the end of the
50
56
  * line, so switching scope looked like it changed nothing at all.
51
57
  */
52
- export declare function registryScopeChoices(config: ResolvedSiteConfig): Choice[];
58
+ export declare function registryScopeChoices(config: ResolvedSiteConfig, registry?: RegistryHint | null): Choice[];
53
59
  /**
54
60
  * Scope choices for adding one package — the same two-way choice, the same
55
61
  * two glyphs, the same order as the registry bar and the package cards.
@@ -47,10 +47,15 @@ export function installChoices(config) {
47
47
  * the `helm repo add` ergonomic. `null` when `registry` is unconfigured,
48
48
  * which means the index has no public URL to hand out; the block is omitted
49
49
  * rather than guessed.
50
+ *
51
+ * `registry` defaults to the site's own, and is a parameter so a page can draw
52
+ * the same bar for a *different* index: a corporate setup guide that hands out
53
+ * its own index in the hero and the public one further down needs two bars
54
+ * differing in nothing but this argument.
50
55
  */
51
- export function registryAddCommand(config) {
52
- return config.registry
53
- ? `grim config registry add ${config.registry.alias} --index ${config.registry.index}`
56
+ export function registryAddCommand(config, registry = config.registry) {
57
+ return registry
58
+ ? `grim config registry add ${registry.alias} --index ${registry.index}`
54
59
  : null;
55
60
  }
56
61
  /**
@@ -65,8 +70,8 @@ export function registryAddCommand(config) {
65
70
  * flag, and appended after a long `--index <url>` it fell off the end of the
66
71
  * line, so switching scope looked like it changed nothing at all.
67
72
  */
68
- export function registryScopeChoices(config) {
69
- const add = registryAddCommand(config);
73
+ export function registryScopeChoices(config, registry = config.registry) {
74
+ const add = registryAddCommand(config, registry);
70
75
  if (!add)
71
76
  return [];
72
77
  return [
@@ -20,7 +20,12 @@ import {
20
20
  mdiMicrosoftWindows,
21
21
  mdiPenguin,
22
22
  } from "@mdi/js";
23
- import type { ResolvedSiteConfig } from "../../../config.js";
23
+ import type { RegistryHint, ResolvedSiteConfig } from "../../../config.js";
24
+
25
+ // Re-exported so an index's own page can name the shape it passes to the two
26
+ // functions below without reaching past `@grim/lib/*` into the package's
27
+ // internals, which the overlay does not publish.
28
+ export type { RegistryHint };
24
29
 
25
30
  /**
26
31
  * One option in a command bar: what it is called, what it copies, and the
@@ -77,10 +82,18 @@ export function installChoices(config: ResolvedSiteConfig): Choice[] {
77
82
  * the `helm repo add` ergonomic. `null` when `registry` is unconfigured,
78
83
  * which means the index has no public URL to hand out; the block is omitted
79
84
  * rather than guessed.
85
+ *
86
+ * `registry` defaults to the site's own, and is a parameter so a page can draw
87
+ * the same bar for a *different* index: a corporate setup guide that hands out
88
+ * its own index in the hero and the public one further down needs two bars
89
+ * differing in nothing but this argument.
80
90
  */
81
- export function registryAddCommand(config: ResolvedSiteConfig): string | null {
82
- return config.registry
83
- ? `grim config registry add ${config.registry.alias} --index ${config.registry.index}`
91
+ export function registryAddCommand(
92
+ config: ResolvedSiteConfig,
93
+ registry: RegistryHint | null = config.registry,
94
+ ): string | null {
95
+ return registry
96
+ ? `grim config registry add ${registry.alias} --index ${registry.index}`
84
97
  : null;
85
98
  }
86
99
 
@@ -96,8 +109,11 @@ export function registryAddCommand(config: ResolvedSiteConfig): string | null {
96
109
  * flag, and appended after a long `--index <url>` it fell off the end of the
97
110
  * line, so switching scope looked like it changed nothing at all.
98
111
  */
99
- export function registryScopeChoices(config: ResolvedSiteConfig): Choice[] {
100
- const add = registryAddCommand(config);
112
+ export function registryScopeChoices(
113
+ config: ResolvedSiteConfig,
114
+ registry: RegistryHint | null = config.registry,
115
+ ): Choice[] {
116
+ const add = registryAddCommand(config, registry);
101
117
  if (!add) return [];
102
118
  return [
103
119
  { name: "Global", command: `grim --global ${add.slice("grim ".length)}`, Icon: Globe },
@@ -21,6 +21,7 @@ import {
21
21
  import Base from "../../layouts/Base.astro";
22
22
  import VersionMenu from "../../components/VersionMenu.astro";
23
23
  import CommandBar from "../../components/CommandBar.astro";
24
+ import CodeBlock from "../../components/CodeBlock.astro";
24
25
  import { BrandMark } from "../../components/BrandMark.tsx";
25
26
  import { DEPRECATED_MARK, KIND_MARKS, KindMark } from "../../components/KindMark.tsx";
26
27
  import {
@@ -31,7 +32,6 @@ import {
31
32
  mdiMicrosoftVisualStudioCode,
32
33
  } from "@mdi/js";
33
34
  import { withBase } from "../../lib/base";
34
- import { SHIKI_THEMES } from "../../lib/code";
35
35
  import {
36
36
  compareVersions,
37
37
  deprecationNote,
@@ -47,7 +47,6 @@ import {
47
47
  import { addArtifactChoices } from "../../lib/commands";
48
48
  import { data } from "../../lib/data";
49
49
  import { getEntry, render } from "astro:content";
50
- import { Code } from "astro:components";
51
50
 
52
51
  export function getStaticPaths() {
53
52
  return data.packages.map((pkg) => ({
@@ -382,10 +381,10 @@ const headMarkColor = pkg.deprecated
382
381
  ))}
383
382
  </ul>
384
383
  ) : (
385
- <Code
384
+ <CodeBlock
386
385
  code={JSON.stringify(contentsData, null, 2)}
387
386
  lang="json"
388
- themes={SHIKI_THEMES}
387
+ name="contents"
389
388
  />
390
389
  )}
391
390
  </div>
@@ -612,11 +611,14 @@ const headMarkColor = pkg.deprecated
612
611
  gap: var(--grim-space-7);
613
612
  min-width: 0;
614
613
  }
614
+ /* Top-aligned, not centred. Centring pinned the tile to the middle of the
615
+ text block beside it, so every extra line of description pushed the logo
616
+ further down the header — the same package's mark sat at a different
617
+ height on every page. Aligned to the start it keeps its place against the
618
+ name and the byline whatever the description runs to. */
615
619
  .detail-head {
616
620
  display: flex;
617
- /* Stretch, so the logo can take the height of the whole text block
618
- beside it rather than sitting centred against a third of it. */
619
- align-items: stretch;
621
+ align-items: flex-start;
620
622
  gap: var(--grim-space-6);
621
623
  background: var(--grim-color-card);
622
624
  border: var(--grim-border-width) solid var(--grim-color-border);
@@ -626,19 +628,23 @@ const headMarkColor = pkg.deprecated
626
628
  .detail-head-text {
627
629
  min-width: 0;
628
630
  }
631
+ /* One fixed square, the same on every package — a size, not a frame: the
632
+ logo slot draws no background, so what this reserves is the space the
633
+ mark letterboxes into. Sizing it off the text block beside it did not
634
+ work: `aspect-ratio` derives the width from the height the box has
635
+ *before* `align-self: stretch` grows it, so the tile came out 56px wide
636
+ against a 93px-tall header — a portrait chip that hugged the logo instead
637
+ of framing it. A definite square has no such ordering problem, and every
638
+ package's mark then lands at one size whatever its aspect ratio.
639
+
640
+ `.tile.letter` below is the exception that keeps a background, and it is
641
+ not one: there the coloured ground IS the mark, and the initial sitting on
642
+ it would be invisible without it. */
629
643
  .tile {
630
644
  flex: 0 0 auto;
631
- align-self: stretch;
632
- width: auto;
633
- height: auto;
634
- /* Square, sized by the text block it stands beside. Bounded at both
635
- ends: a one-line header must not shrink it to a favicon, and a long
636
- summary must not grow it into the page. */
637
- aspect-ratio: 1;
638
- min-height: 3.5rem;
639
- max-height: 7rem;
645
+ width: 4.5rem;
646
+ height: 4.5rem;
640
647
  border-radius: var(--grim-radius-surface);
641
- object-fit: cover;
642
648
  }
643
649
  .tile.letter {
644
650
  display: flex;