@grimoire-rs/indexer 0.5.3 → 0.5.4

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
@@ -4,393 +4,283 @@ All notable changes to `@grimoire-rs/indexer` are recorded here, in
4
4
  [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) format. This project
5
5
  adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
6
 
7
- > This file starts at `0.5.0`. Releases up to and including `v0.4.4` shipped
8
- > before it existed and are not reconstructed here — the git tags and
9
- > `git log v0.4.3..v0.4.4` are the record for those. Every release from the
10
- > next one on is written down here.
11
-
12
7
  Component overrides sit in the **Unstable** tier of the
13
8
  [stability table](docs/reference/theme-overlay.md#stability): their file paths
14
9
  and props may move in a minor release, and this file is where that is
15
- announced. Everything in the Contract tier moves only in a major.
10
+ announced. Everything in the Contract tier moves only in a major — so a commit
11
+ that moves one says which component and where, old → new, in its subject or
12
+ body, because that is all a reader gets here.
13
+
14
+ ## [0.5.4] - 2026-09-01
16
15
 
17
- An entry for such a move carries both halves or it announces nothing an
18
- overrider can act on: the component's name, and the move itself as old → new —
19
- `PackageRow`: `components/PackageRow.tsx` → `components/list/PackageRow.tsx`,
20
- props `{ pkg }` → `{ pkg, compact }`.
16
+ ### Added
21
17
 
22
- ## [Unreleased]
18
+ - Publish the forge host in providers.rating_host *(ratings)*
19
+ - Carry the picked scope into the add-registry link *(renderer)*
23
20
 
24
- ## [0.5.3] - 2026-09-01
21
+ ## [0.5.3] - 2026-08-31
25
22
 
26
23
  ### Added
27
24
 
28
- - **Search is fuzzy, and reaches every field an index publishes.** It was a
29
- case-insensitive substring test over the seven fields the island is handed, so
30
- a typo matched nothing and a package could not be found by its licence,
31
- vendor, repository, authors or documentation. A query now splits on spaces
32
- into terms that are AND-combined and order-independent, and each may land on a
33
- different key — `astro grimoire` finds a package whose keywords carry one word
34
- and whose vendor carries the other. The full record set comes from
35
- `/all.json`, already published and already a frozen public URL, so it costs
36
- the island no payload: it and the matcher load on the first keystroke and not
37
- before, as an 8.5 KB gzipped chunk nothing preloads. Until they land, and if
38
- the fetch fails, the substring pass stands — the field is never dead.
39
- - **`relevance`, a sort mode**, and the reason the fuzzy pass is worth having:
40
- it orders by match score. It is offered, stored and server-rendered like every
41
- other mode, and with no query to rank it answers alphabetically — the order
42
- the catalog opens on anyway — so a reader can leave it set and have every
43
- later search come back ranked.
44
- - **A clear button in the search field.** Chromium and Safari draw one of their
45
- own, in the UA's styling, in the same corner the `/` hint occupies, and it
46
- cannot be restyled beyond hiding. So it is hidden and the control is rendered:
47
- the hint's exact box, carrying an X instead of the key. The two never coexist,
48
- and clicking it returns focus to the field.
49
- - **`sitemap.xml`, and a `robots.txt` naming it.** Written by `buildSite` from
50
- the package list it already holds: every package page stated once, with no
51
- crawler obliged to render an island or scroll it. This site emitted neither
52
- before, so the landing page was the only path to a detail page — and the
53
- windowed catalog below would have made that path a thin one.
54
- - **A `<noscript>` catalog on the landing page**, listing every package. Without
55
- JavaScript the island never hydrates, so the reader would otherwise get one
56
- slice of cards under a toolbar that does nothing.
25
+ - --bulk, to look at the catalog at corporate size *(dev)*
26
+ - Fuzzy search across every field, with a relevance order *(renderer)*
27
+ - Give the search field its own clear button *(renderer)*
57
28
 
58
29
  ### Changed
59
30
 
60
- - **The catalog builds a viewport of the index, not all of it.** At 500
61
- packages it took 1.9s to appear for a reader whose stored view is the list and
62
- 1.5s to switch views; both are now under 50ms, and the landing page goes from
63
- 3.42MB to 795KB. The island builds one viewport's worth and *grows* the slice
64
- as a sentinel below the list comes into view — a growing slice rather than
65
- true virtualization, so an item that has been built stays built and find-in-page
66
- keeps working over everything reached. The keyword rail's two geometry-reading
67
- layout effects are gated on a signature of the visible chips rather than
68
- running on every commit, and the filter, sort and rail chain is memoized.
69
- - **The island is shipped only the fields a card renders**, through a new
70
- `CardPackage` type and `cardPackage()` projection exported from
71
- `lib/catalog.ts`. Astro serializes island props into an attribute on
72
- `<astro-island>`, so every field handed to `<Catalog>` was paid for twice on
73
- the landing page: at 250 packages the attribute was 323,044 bytes, 17.5% of
74
- the page, most of it fields no card or row reads. It is 201,554 now (-37.6%).
75
- Three Unstable-tier components take the narrower type — `PackageCard`,
76
- `PackageRow` and `CardLogo`, props `{ pkg: CatalogPackage }` →
77
- `{ pkg: CardPackage }`. An override reading `license`, `authors`, `vendor`,
78
- `documentation`, `compatibility`, `revision`, `support`, `repository`,
79
- `title`, `owner`, `schema`, `tags` or an unknown enrichment key off `pkg` now
80
- fails to compile rather than rendering `undefined` in the browser; the detail
81
- pages are untouched, and read the full record as before.
82
- - **The list view's columns are sized for the description.** The two marks are
83
- `min-content` rather than `auto` and the name is `fit-content(14rem)` rather
84
- than `minmax(8rem, 14rem)` — that 8rem floor made a list of short names pay a
85
- fixed 8rem, so the description started after a column of empty space. The
86
- row's gutter drops a step with them.
31
+ - Ship the island only the fields a card renders *(renderer)*
32
+ - Build a viewport of the catalog, not all of it *(renderer)*
87
33
 
88
34
  ### Fixed
89
35
 
90
- - **The list view's rows lost their subgrid columns.** `content-visibility:
91
- auto`, added as a paint bound, applies size containment and a size-contained
92
- element cannot be a subgrid, so `grid-template-columns: subgrid` degraded to
93
- `none` in silence and every row computed its own equal-sixths grid. Every cell
94
- lined up perfectly and the alignment was fake. The bound comes off; what it
95
- was aimed at is held by `loading="lazy"` on the logo and by the window above.
96
- - **The agent badge cleared WCAG AA.** `--grim-color-kind-agent` was `#b0641a`,
97
- 4.484:1 against the white card and so under AA's 4.5 for the 11.5px badge it
98
- tints; it is `#a05c17` now, 5.20:1. Its four siblings were audited at the same
99
- time and clear it unaided, so they are unchanged, and so is the dark palette.
100
- - **A stored `table` view no longer flips after the first paint.** The pre-paint
101
- gate listed the sort, dir and deprecated preferences only, so a reader whose
102
- stored view is the list got the server's cards painted first and the island's
103
- table a moment later. `?kw=` had the same gap on the other half of the
104
- condition — the parameter a keyword chip on a package page links to was never
105
- checked. Both lists are pinned by a test now.
106
- - **The sort control's focus ring stays off a mouse click.** Chromium matches
107
- `:focus-visible` on a `<select>` after a plain click, so the accent ring lit on
108
- click and stayed lit beside the thin neutral chips until the reader clicked
109
- elsewhere. No selector distinguishes focus that arrived from a pointer, so the
110
- element marks itself on `pointerdown` and a keypress or a blur clears it. The
111
- keyboard path keeps both the ring and its focus.
36
+ - Hold the paint for a stored view and a kw deep link *(renderer)*
37
+ - Lift the agent badge to WCAG AA, and ratchet the floors *(renderer)*
38
+ - Verify the pack shape under both npm majors the matrix ships *(ci)*
39
+ - Restore the list view's subgrid columns *(renderer)*
40
+ - Keep the sort ring off a pointer interaction *(renderer)*
112
41
 
113
42
  ## [0.5.2] - 2026-08-31
114
43
 
115
44
  ### Fixed
116
45
 
117
- - **The sort control drew two borders when clicked.** Hover tints a control's
118
- border to the accent and focus drew a 2px accent outline standing 1px clear
119
- of it, so anyone who reached the control with a mouse had both at once — one
120
- state reading as two lines with a gap between them. The ring merges onto the
121
- border now, the way the card's focus state already did, and the offset comes
122
- off every bordered control in the toolbar together rather than off the one
123
- that showed it: the search field, the keyword overflow menu's search, both
124
- sort halves and both view picks. Keyword chips gain the same ring, having
125
- taken the browser's default until now.
126
- - **A long address no longer wraps the card's head onto a second line.** A
127
- namespace breaks at its own slashes and dots, so a wide one took two rows and
128
- left the card taller than its neighbours with the tail of the address against
129
- the padding edge. It stays on one line and the overflow is trimmed from the
130
- *front* — the registry host is on every address in an index and the
131
- repository at the tail is what tells two apart — with the whole of it in the
132
- element's `title`.
133
- - **Keyword chips could stutter, or stop part-way through the rail's slide.**
134
- The rail's FLIP pass inverted each chip with an inline `transition: none` and
135
- a `translate`, then dropped both on the next animation frame; any commit
136
- landing inside that window left the chip carrying the offset with its
137
- transition disabled, and nothing else took those off. It also measured seats
138
- with `getBoundingClientRect`, which reports where a chip is *drawn*, so a
139
- chip caught mid-slide recorded its animated box and the next inversion
140
- compounded the error instead of correcting it. Neither window was rare: the
141
- rail's own fit measurement re-commits whenever a rescore changes how many
142
- chips fit. Seats now come from `offsetLeft`/`offsetTop`, which ignore
143
- transforms and scroll, and the slide is a Web Animation — it starts without a
144
- frame, writes nothing to `style`, and clears itself when it finishes or is
145
- cancelled. `prefers-reduced-motion` is honoured by the effect rather than by
146
- the stylesheet, and `--grim-duration-slow` is still its length.
147
-
148
- ## [0.5.1] - 2026-08-31
46
+ - Merge the focus ring onto the control's own border *(renderer)*
47
+ - Trim a long card address from the front *(renderer)*
48
+ - Run the keyword rail's slide as a Web Animation *(renderer)*
49
+
50
+ ## [0.5.1] - 2026-08-30
149
51
 
150
52
  ### Added
151
53
 
152
- - **`grim-indexer dev --host [addr]`.** Bare, it binds every interface; with a
153
- value, that address. Without it the server binds loopback only Astro's own
154
- default which is unreachable from a dev container, a VM or a WSL guest,
155
- where the port forwards and the connection then hangs against a socket that
156
- is not listening for it. `npm run dev -- --host` takes the same two forms.
157
- Bare `--host` puts the preview on your network; the CLI reference says so.
158
- - **`CodeBlock.astro`**, a reusable code block: the site's own frame, the same
159
- Shiki theme pair every rendered README uses, the same copy button and toast,
160
- and an optional VS Code button. Props
161
- `{ code, lang?, name?, vscodeHref?, vscodeLabel? }`; `vscodeHref` is a plain
162
- URL, so `vscodeUrl`, `vscodeVoteUrl`, `addRegistryUrl` and a hand-written
163
- deep link all work. Worked example in
164
- [Reuse shipped components](docs/how-to/reuse-components.md#code-blocks).
165
- - **`data-slot="code-block"`**, for it. Contract tier, like every other slot.
166
- - **A second argument on `registryAddCommand` and `registryScopeChoices`**,
167
- the registry to build the command for — defaulting to the one in your config,
168
- so nothing existing changes. It is what lets a setup page draw the "add
169
- registry" bar for an index *other* than the site's own: a corporate index in
170
- the hero and the public one further down.
171
- - **A clear button on the keyword filters**, in the catalog toolbar. Rendered
172
- only while a keyword is picked, and it lifts the keywords alone — Escape is
173
- still what clears the search and the kinds with them.
54
+ - Add a --host flag to dev *(cli)*
55
+ - Add CodeBlock, a code block an index's page can use *(renderer)*
56
+ - Let the registry command builders take another index *(renderer)*
57
+ - Add a clear button to the keyword filters *(renderer)*
58
+
59
+ ### Changed
60
+
61
+ - Keep off-screen cards out of layout and paint *(renderer)*
62
+
63
+ ### Documentation
64
+
65
+ - Record the unreleased changes *(changelog)*
174
66
 
175
67
  ### Fixed
176
68
 
177
- - **The keyword overflow menu opened invisibly.** Its panel was an absolutely
178
- positioned child of the toolbar's filter row, which is a scroll container, so
179
- the panel was cropped to the row and stretched the row's scroll extent a
180
- menu nobody could see, with a stray horizontal and vertical scrollbar to show
181
- for it. It is a popover now, which puts it in the top layer, outside every
182
- ancestor's `overflow`, and brings Escape and light dismiss with it. The
183
- trigger is a `<button>` rather than a `<summary>`, so the toolbar's arrow-key
184
- navigation now reaches it.
185
- - **The detail page's logo drifted down under a long description.** The header
186
- centred the tile against the text beside it, so the same package's mark sat
187
- at a different height on every page. It holds against the title now.
188
- - **Dropped the tinted square behind a package logo**, on the cards, the list
189
- rows and the detail header alike. A logo is a designed mark already sitting
190
- on its own ground, so the slot framed it twice — most visibly for the many
191
- logos that are themselves a rounded square, which then sat inside a slightly
192
- larger one. The slot still reserves the same box, so nothing shifts. The
193
- dashed frame the broken-logo state drew goes with it — it read as a fault in
194
- the layout rather than in the image; the slashed glyph is what says the logo
195
- failed, and `role="img"` with a label is what announces it. The
196
- initial-letter tile a package with no logo gets is unchanged: there the
197
- coloured ground is the mark.
198
- - **The dev server's URL is a legal URL when the server binds an IPv6
199
- address.** An unbracketed literal reads its own colons as a port, so
200
- `http://::1:4321/` threw `ERR_INVALID_URL` at the first `new URL` against it
201
- and took `dev:smoke` down with a message naming neither the address nor the
202
- host. Wildcard binds still become `localhost`; anything carrying a colon is
203
- bracketed.
204
- - **The detail page's logo tile is a fixed square.** `aspect-ratio: 1` derived
205
- its width from the height the box had *before* `align-self: stretch` grew it,
206
- so it rendered 56x93 beside a logo and 56x70 beside a letter — portrait,
207
- never square, and narrow enough to read as bounding the image rather than
208
- framing it.
209
- - **The list row's glyphs sit on the row's centre.** `vertical-align: middle`
210
- aligns to half the parent's x-height, about 2px under the line's true middle
211
- at this step, and the upvote arrow's optical nudge added a third pixel the
212
- same way. The kind and rating cells are flex boxes now and centre their
213
- contents as boxes.
214
- - **Keyword chips could freeze part-way through the rail's slide.** The rail
215
- animates a rescore by inverting each chip with an inline `translate` and
216
- dropping it on the next animation frame. A rescore that also changes how many
217
- chips fit commits a second time, and that commit cancelled the frame before
218
- it ran — leaving every moved chip parked at its offset with transitions
219
- disabled. Deselecting the last keyword hit it most often, because that
220
- rescore is the largest. The offsets are now cleared before each measurement
221
- and on cleanup, so an interrupted slide resolves instead of sticking.
222
- - **Switching between the cards and list views repainted the whole catalog.**
223
- Off-screen cards are skipped until they are scrolled to. The two views share
224
- no element types, so a switch still rebuilds every item — past a few hundred
225
- packages the answer is windowing the list, and this is not that.
69
+ - Bracket an IPv6 address in the dev server's URL *(renderer)*
70
+ - Make the detail page's logo tile a fixed square *(renderer)*
71
+ - Centre the list row's glyphs on the row, not the x-height *(renderer)*
72
+ - Open the keyword overflow menu in the top layer *(renderer)*
73
+ - Stop keyword chips freezing part-way through the rail's slide *(renderer)*
74
+ - Hold the detail logo against the title, and stop framing it *(renderer)*
226
75
 
227
76
  ## [0.5.0] - 2026-08-30
228
77
 
229
78
  ### Added
230
79
 
231
- - **Theme overlay.** A `theme/` directory in an index repo is copied over the
232
- renderer's staged sources before every build. A file at a path the renderer
233
- does not ship adds one `theme/pages/setup.astro` becomes `/setup/` — and a
234
- file at a path it does ship replaces it. Structure is customizable now, not
235
- just CSS. Only on the staged-copy path: a caller-supplied `srcDir` is left
236
- alone.
237
- - **Two paths the overlay refuses**, `theme/lib/**` and a root
238
- `theme/content.config.ts`, so the renderer stays free to move its own helpers
239
- and its one content-collection entrypoint. Each prints a line to stderr and
240
- the build still succeeds. The same rule applies to `dev`'s mirror, because a
241
- deny-list only one of the two writers honours lets an author develop against
242
- a file the build then drops in silence. It is a compatibility guard, not a
243
- security boundary: the copy does not follow symlinks either way.
244
- - **`@grim/*` import specifier**, resolving to the renderer's sources so an
245
- added page reaches the layout, components and helpers without a relative
246
- path that encodes how deep it sits under `pages/`. It resolves *after* the
247
- overlay, so an index that replaced a component imports its own.
248
- - **`@grim-original/*`**, the same tree as it was *before* the overlay ran, so
249
- an override can wrap the file it replaced instead of owning it outright.
250
- `@grim/*` cannot serve that purpose once the theme has taken the path, and
251
- the package's `exports` do not publish the Astro sources for a deep import.
252
- - **One stderr line per overlaid path that replaces a shipped file**, naming
253
- the path and the indexer version that shipped it. That is what makes the
254
- Unstable tier below honest without a manifest. A path that only adds a file
255
- is not logged.
256
- - **`dev` mirrors `theme/**` edits** into the staged tree, so a page reloads as
257
- it is saved. It creates `theme/` first if it is absent — a directory added
258
- after the server started now mirrors instead of 404ing forever — and removes
259
- it again on shutdown if you left it empty. Deletes are still not mirrored;
260
- restart for those. A fault in the platform's recursive-watch backend disables
261
- mirroring with a message rather than taking the server down.
262
- - **`nav` config key.** An ordered `{label, href}` list that takes the header
263
- over. Unset, it synthesizes exactly what the header has always shown, so an
264
- index that predates the key renders unchanged; `[]` leaves the theme toggle
265
- standing alone.
266
- - **`notice` config key.** One line above the page content, in the site's own
267
- width, on every page, carrying `data-slot="site-notice"`. Named `notice`
268
- rather than `banner` because `--grim-color-banner-*` is the existing amber
269
- palette for the deprecated-package banner, and a key called `banner` would
270
- invite an index runner to override that token family by mistake.
271
- - **`nav[].external` and `footerLinks[].external`.** Optional per entry, and it
272
- decides only the new-tab affordance — the deployment base prefix is applied
273
- either way. Unset keeps the old inference from the href's shape. `false` is
274
- the case that inference cannot express: an absolute URL that is still your
275
- own site, a staging host or an intranet mirror.
276
- - **A build-time warning for a `nav` or `footerLinks` href that leads nowhere.**
277
- A `/`-rooted href matching no emitted route and no `public/` file prints
278
- `nav[0].href "/setup/": nothing is published at that path — the link will
279
- 404`. It warns and never fails: a path served by something outside the build
280
- is indistinguishable from a typo, and refusing a whole site over a footer
281
- typo is worse than the typo. Checked after the build, which is the one moment
282
- both the emitted routes and every `public/` layer exist at once.
283
- - **A warning for an unrecognised top-level key in `index.config.json`**,
284
- naming the key. Without it a typo — or a config predating the `banner` →
285
- `notice` rename — loaded clean and silently rendered nothing.
286
- - **`init` scaffolds `theme/README.md` and a `tsconfig.json`** mapping
287
- `@grim/*` for editors. The README states where a page goes and which parts of
288
- the overlay are promised, which a `.gitkeep` could not; both reserve `theme/`
289
- for git equally well. `theme/pages/` is no longer created empty. The build
290
- does not read that tsconfig.
291
- - **A documentation site** (MkDocs Material, Diátaxis), built `--strict` as the
292
- docs' pre-merge gate and published to GitHub Pages. `task docs:build`,
293
- `docs:serve`, `docs:clean` — deliberately not part of `task check`, which
294
- must stay runnable with no Python toolchain.
295
- - **Individually replaceable components**: `SiteHeader`, `SiteFooter`,
296
- `CommandBar`, `CopyButton`, `CardLogo`, `PackageCard`, `PackageRow`.
297
- - **This changelog**, which two docs pages already made the sole mitigation for
298
- the Unstable tier. It ships in the npm tarball, so the copy that matches your
299
- pinned version is `node_modules/@grimoire-rs/indexer/CHANGELOG.md`.
300
- - **`npm run typecheck:tests`**, type-checking the `test` tree under its own
301
- `tsconfig.test.json`. Deliberately not wired into `task check` yet: the tree
302
- has 95 real type errors, which is its own piece of work.
80
+ - Carry the rating thread URL into the catalog *(renderer)*
81
+ - Redraw the package card, and keep the view across a visit *(renderer)*
82
+ - Give the detail page its right column back *(renderer)*
83
+ - Pick keyword chips by splitting power *(renderer)*
84
+ - Give the catalog a keyword facet and a list view *(renderer)*
85
+ - Ship square, behind one radius knob *(renderer)* **BREAKING**
86
+ - **Migration:** every corner is square by default. `--grim-radius-base` is the whole decision `0px` now, `4px` restores the rounding of `0.4.x` and earlier in one line. The four measurement steps derive from it and hold their ratios; overriding one step directly still beats the derivation, so a consumer wanting square surfaces and rounded controls sets that step and ignores the knob. Four steps at 4/6/8/10px was a distinction nobody perceived — they were only pickable after their roles were written down, and the package list had already ended up on the wrong one silently. Square reads as the developer tool this is, and `pill` stays off the knob because a chip is a
87
+ - Show the package logo in the list view *(renderer)*
88
+ - Add nav and notice, behind one URL guard for every link value *(config)* **BREAKING**
89
+ - **Migration:** `footerLinks[].href` now accepts a site-root path as well as an absolute URL, and both link keys are validated when the config loads, so a
90
+ - Lay an index repo's theme/ over the shipped sources *(renderer)*
91
+ - Scaffold theme/README.md and an editor tsconfig that loads *(init)* **BREAKING**
92
+ - **Migration:** `init` no longer creates `theme/pages/.gitkeep`. An index scaffolded before this keeps its own copy; nothing removes it.
303
93
 
304
94
  ### Changed
305
95
 
306
- - **`footerLinks[].href` now accepts a site-root path** (`/setup/`) as well as
307
- an absolute `http(s)` URL previously `http(s)` only. `nav` takes the same
308
- rule from one shared validator, and so now do `logo` and `favicon`, which had
309
- three different answers between them and one that validated nothing. Both
310
- link keys are checked when the config *loads*, so a `javascript:` or
311
- protocol-relative href fails the build with the key that carried it, rather
312
- than reaching a rendered anchor. A bare relative path is still refused: it
313
- would resolve against whichever page carries the link, and the detail pages
314
- sit two levels deep.
315
- - **`/\host/x` and userinfo are refused everywhere a config value becomes a
316
- URL.** Browsers read `/\host/x` the way they read `//host/x`, and
317
- `https://good.test@evil.test/` resolves to `evil.test` while reading as
318
- `good.test`. Two of the four validators already rejected userinfo; all four
319
- do now.
320
- - **Footer links go through the deployment base prefix, and honour the same
321
- new-tab rule as the header.** They did neither before. **This changes an
322
- existing site:** an `https://` footer link now opens in a new tab. Set
323
- `"external": false` on the entry to keep it in the same tab.
324
- - **`Base.astro` no longer contains the header and footer markup** — they are
325
- `SiteHeader.astro` and `SiteFooter.astro`. Neither takes props: everything
326
- they draw is build-time config. The head, the centered `main`, the copy toast
327
- and every default style still come from the layout.
328
- - **The platform-preselect loop moved into `Base.astro`**, so a `<CommandBar
329
- detect />` on a page you added under `theme/pages/` preselects the visitor's
330
- own platform. It used to run only on the shipped landing page, so the same
331
- markup elsewhere silently showed the configured first choice instead.
332
- - **`Catalog.tsx` no longer contains the card and row markup** — they are
333
- `PackageCard.tsx` and `PackageRow.tsx`, and they *do* take props, because a
334
- component that hydrates in the browser cannot read the build-time payload.
335
- Those props are the contract the Unstable tier is about.
336
- - **The install/registry/package command boxes are one `CommandBar`**, with
337
- their choices derived in `@grim/lib/commands` — so a page an index adds can
338
- draw the site's real install bar from the site's own config instead of
339
- restating the command in a code block that drifts.
340
- - **The renderer ships square.** `--grim-radius-base` is `0px`; set it to `4px`
341
- to restore the rounding of `0.4.x` and earlier in one line.
342
- - **Radius tokens are named for their role**: `--grim-radius-sm`/`-md`/`-lg`/
343
- `-xl` are now `-code`, `-inset`, `-control` and `-surface`, and they derive
344
- from `--grim-radius-base` rather than carrying their own values. `-pill` is
345
- unchanged and off the knob. An override under an old name is silently
346
- ignored, like any unknown property.
347
- - **The list view leads with the package logo**, then the name, then the kind
348
- mark.
349
- - **The README is a pointer to the docs site.** The theming contract, the stats
350
- sidecar schema and the CLI reference moved into `docs/`.
351
- - **The scaffolded `tsconfig.json` sets `module` and `moduleResolution`**
352
- `esnext`/`bundler`, what actually reads those specifiers and no longer sets
353
- `baseUrl`, which is deprecated on the pinned TypeScript and made the file
354
- fail to load. It stays free of `extends` on purpose.
355
- - **The docs workflow splits `configure-pages` into its own job**, so the job
356
- that resolves and executes unpinned transitive PyPI packages holds
357
- `contents: read` and nothing else — `id-token: write` is a job-wide grant.
358
- lychee now checks the external links `mkdocs --strict` never resolves and,
359
- with `--include-fragments`, the `#anchor` half of the internal ones, which
360
- `--strict` logs at INFO and exits 0 on.
361
- - **eslint and vitest skip `.agents/worktrees/`.** Both tools walk the tree
362
- themselves rather than asking git, and neither knew about a path `.gitignore`
363
- had carried all along: eslint found a `tsconfig.json` per worktree and
364
- reported a parse error on every file in the repo, and vitest collected and
365
- ran each worktree's whole suite. A `.gitignore` entry and a tool's own ignore
366
- list are two independent claims.
96
+ - Name the radius tokens for their role *(renderer)* **BREAKING**
97
+ - **Migration:** `--grim-radius-sm`, `-md`, `-lg` and `-xl` are now `--grim-radius-code`, `-inset`, `-control` and `-surface`. `-pill` is unchanged and no value moved. A `customCss` override under an old name is silently ignored, like any unknown custom property; `README.md` carries the migration note. Every other family in `tokens.css` names the role a value plays rather than its size — that is the rule this sheet states about itself, and the radius scale was the one family breaking it. The cost was not cosmetic: with nothing saying which step a surface should take, the package list ended up on the control step next to cards on the surface step, and the two corners disagreed. That is fixed here too. The contract comment now publishes what each step is for, and warns against picking by eye — the same radius on a shorter box reads as more curve, so two corners that look different are often the same step.
98
+ - Lead the list row with logo, name, then kind *(renderer)*
99
+ - Put the kind mark before the name in the list *(renderer)*
100
+ - Make the header, footer, command bar and cards replaceable *(renderer)* **BREAKING**
101
+ - **Migration:** an `https://` footer link now opens in a new tab. Set
102
+
103
+ ### Documentation
104
+
105
+ - Write down the local loop -- direnv, task, and the dev index
106
+ - Publish a documentation site, and start a changelog
107
+
108
+ ### Fixed
109
+
110
+ - Hang the kind watermark off the corner by a share of its size *(renderer)*
111
+ - Point the dev fixture at the real extension id *(dev)*
112
+
113
+ ## [0.4.4] - 2026-08-29
114
+
115
+ ### Fixed
116
+
117
+ - Upload the tally -- a dotfile artifact was silently dropped *(ratings)*
118
+
119
+ ## [0.4.3] - 2026-08-28
120
+
121
+ ### Fixed
122
+
123
+ - Stop locking threads by default -- a locked one cannot be voted on *(ratings)* **BREAKING**
124
+
125
+ ## [0.4.2] - 2026-08-28
126
+
127
+ ### Added
128
+
129
+ - Publish zero-vote threads so a first vote can be cast *(ratings)* **BREAKING**
130
+
131
+ ## [0.4.1] - 2026-08-28
132
+
133
+ ### Added
134
+
135
+ - Make the whole theming surface a token contract *(renderer)* **BREAKING**
136
+ - **Migration:** every CSS custom property is renamed. The unnamespaced tokens (`--accent`, `--bg`, `--fg`, and the rest) are now `--grim-color-*`, and space, type, radius, border width, motion and elevation become overridable for the first time under `--grim-space-*`, `--grim-text-*`, `--grim-radius-*`, `--grim-border-width`, `--grim-duration-*` and `--grim-shadow-*`. A `theme.css` written against 0.4.0 or earlier stops applying silently rather than erroring.
137
+ - Publish a checkpoint, and seed the sidecars from it *(enrich)*
138
+
139
+ ### Fixed
140
+
141
+ - Stop reading a large page as a transport failure *(ratings)*
142
+ - Stop every run dying on PAGE_SIZE at import *(ratings)*
143
+
144
+ ## [0.4.0] - 2026-08-26
145
+
146
+ ### Added
147
+
148
+ - Publish a stats.json sidecar from the index's own forge (#5) *(ratings)*
149
+ - Carry grim's annotation fields, and date every package *(enrich)*
150
+ - Show provenance and support, and publish the updated signal *(renderer)*
151
+
152
+ ### Documentation
153
+
154
+ - Document the second stat, and release 0.4.0
155
+
156
+ ## [0.3.1] - 2026-07-30
157
+
158
+ ### Documentation
159
+
160
+ - Point at the index template repository
161
+
162
+ ## [0.3.0] - 2026-07-30
163
+
164
+ ### Added
165
+
166
+ - Own the pipeline in the index repo, and harden the gate *(ci)* **BREAKING**
167
+ - **Migration:** the reusable workflows `grimoire-rs/indexer/.github/workflows/index-{pages,validate}.yml` and the GitLab remote include are gone. An index scaffolded on 0.2.x keeps working on its pinned tag; to move, bump the dependency, add a `ci` block to index.config.json, run `npm run ci`, and delete the old thin callers - `ci --check` reports them as stale.
168
+
169
+ ## [0.2.2] - 2026-07-28
170
+
171
+ ### Fixed
172
+
173
+ - Stop defaulting the keys that name one specific index *(config)*
174
+
175
+ ## [0.2.1] - 2026-07-28
367
176
 
368
177
  ### Fixed
369
178
 
370
- - **Scaffold `npm install` build was broken end to end.** An index repo has
371
- its own `node_modules`, and the staged root Astro builds in sits inside it,
372
- so `preact-render-to-string` resolved to a second copy of preact. The first
373
- hook rendered died with `Cannot read properties of undefined (reading
374
- 'context')`, in a stack naming preact and lucide and nothing that leads back
375
- to dependency resolution. Every preact-touching module is pinned to one copy
376
- now, and `preact-render-to-string` is a declared dependency so it resolves
377
- under an isolated install layout too.
378
- - **A replaced header with no theme toggle no longer breaks the rest of the
379
- page.** The `#theme-toggle` lookup in `Base.astro` is null-safe, so the copy
380
- buttons, the picker wiring and the toast still run.
381
- - **A `tsconfig.json` in the index repo that cannot be parsed is now named.**
382
- It made the build fail while rendering a page with an error about preact; the
383
- usual cause is an `extends` pointing into a `node_modules` that has not been
384
- installed yet.
385
- - **`dev` cleans up after itself.** Stopping the server is idempotent, and a
386
- failure part-way through staging removes the scratch directory rather than
387
- leaving it in the index repo. One case is not fixable from inside shutdown
388
- and is written up in the docs: interrupting `dev` within about a second of
389
- boot can leave a `.index-*` holding Vite's `deps_temp_<hash>`, which the
390
- dependency optimizer recreates after the removal has already returned.
391
-
392
- [Unreleased]: https://github.com/grimoire-rs/indexer/compare/v0.5.3...HEAD
179
+ - Stop gitignoring the index's own public/ directory *(init)*
180
+
181
+ ## [0.2.0] - 2026-07-28
182
+
183
+ ### Added
184
+
185
+ - Write a contents sidecar from the artifact payload *(enrich)*
186
+ - Add a header logo, and let an index drop the attribution *(config)*
187
+ - Rebuild the hero and the package page *(renderer)*
188
+
189
+ ## [0.1.9] - 2026-07-28
190
+
191
+ ### Added
192
+
193
+ - Offer the index as a one-click VS Code add-registry link *(renderer)*
194
+
195
+ ## [0.1.8] - 2026-07-28
196
+
197
+ ### Fixed
198
+
199
+ - Hand the gate every changed path, unfiltered *(validate)*
200
+
201
+ ## [0.1.7] - 2026-07-28
202
+
203
+ ### Added
204
+
205
+ - Read package READMEs, logos and versions from the registry (#1) *(enrich)*
206
+
207
+ ## [0.1.6] - 2026-07-28
208
+
209
+ ### Fixed
210
+
211
+ - Give the GitLab gate the git it shells out to *(ci)*
212
+
213
+ ## [0.1.5] - 2026-07-28
214
+
215
+ ### Fixed
216
+
217
+ - Resolve every internal URL against the site's base path *(renderer)*
218
+
219
+ ## [0.1.4] - 2026-07-28
220
+
221
+ ### Documentation
222
+
223
+ - Say the gate does not cover a first-party announce *(init)*
224
+ - Record what the live end-to-end trial settled
225
+
226
+ ### Fixed
227
+
228
+ - Point --with-skills announce at the scaffolded repo *(init)*
229
+ - Name the status check branch protection can require *(init)*
230
+
231
+ ## [0.1.3] - 2026-07-28
232
+
233
+ ### Fixed
234
+
235
+ - Ship the reusable workflows the scaffold points at *(ci)*
236
+
237
+ ## [0.1.2] - 2026-07-28
238
+
239
+ ### Fixed
240
+
241
+ - Declare the repository provenance attests to
242
+
243
+ ## [0.1.1] - 2026-07-28
244
+
245
+ ### Added
246
+
247
+ - Add the grimoire-index renderer, gate and CLI
248
+
249
+ ### Changed
250
+
251
+ - Publish as @grimoire-rs/indexer
252
+ - Name the binary grim-indexer
253
+
254
+ ### Documentation
255
+
256
+ - State the real status and warn off the broken 0.1.0
257
+
258
+ ### Fixed
259
+
260
+ - Declare a bin npm will not silently strip
261
+
262
+ [0.5.4]: https://github.com/grimoire-rs/indexer/compare/v0.5.3...v0.5.4
393
263
  [0.5.3]: https://github.com/grimoire-rs/indexer/compare/v0.5.2...v0.5.3
394
264
  [0.5.2]: https://github.com/grimoire-rs/indexer/compare/v0.5.1...v0.5.2
395
265
  [0.5.1]: https://github.com/grimoire-rs/indexer/compare/v0.5.0...v0.5.1
396
266
  [0.5.0]: https://github.com/grimoire-rs/indexer/compare/v0.4.4...v0.5.0
267
+ [0.4.4]: https://github.com/grimoire-rs/indexer/compare/v0.4.3...v0.4.4
268
+ [0.4.3]: https://github.com/grimoire-rs/indexer/compare/v0.4.2...v0.4.3
269
+ [0.4.2]: https://github.com/grimoire-rs/indexer/compare/v0.4.1...v0.4.2
270
+ [0.4.1]: https://github.com/grimoire-rs/indexer/compare/v0.4.0...v0.4.1
271
+ [0.4.0]: https://github.com/grimoire-rs/indexer/compare/v0.3.3...v0.4.0
272
+ [0.3.1]: https://github.com/grimoire-rs/indexer/compare/v0.3.0...v0.3.1
273
+ [0.3.0]: https://github.com/grimoire-rs/indexer/compare/v0.2.2...v0.3.0
274
+ [0.2.2]: https://github.com/grimoire-rs/indexer/compare/v0.2.1...v0.2.2
275
+ [0.2.1]: https://github.com/grimoire-rs/indexer/compare/v0.2.0...v0.2.1
276
+ [0.2.0]: https://github.com/grimoire-rs/indexer/compare/v0.1.9...v0.2.0
277
+ [0.1.9]: https://github.com/grimoire-rs/indexer/compare/v0.1.8...v0.1.9
278
+ [0.1.8]: https://github.com/grimoire-rs/indexer/compare/v0.1.7...v0.1.8
279
+ [0.1.7]: https://github.com/grimoire-rs/indexer/compare/v0.1.6...v0.1.7
280
+ [0.1.6]: https://github.com/grimoire-rs/indexer/compare/v0.1.5...v0.1.6
281
+ [0.1.5]: https://github.com/grimoire-rs/indexer/compare/v0.1.4...v0.1.5
282
+ [0.1.4]: https://github.com/grimoire-rs/indexer/compare/v0.1.3...v0.1.4
283
+ [0.1.3]: https://github.com/grimoire-rs/indexer/compare/v0.1.2...v0.1.3
284
+ [0.1.2]: https://github.com/grimoire-rs/indexer/compare/v0.1.1...v0.1.2
285
+ [0.1.1]: https://github.com/grimoire-rs/indexer/tree/v0.1.1
286
+
package/README.md CHANGED
@@ -95,6 +95,32 @@ task dev -- --help
95
95
 
96
96
  `task --list` has the rest; `task check` is the whole gate CI runs.
97
97
 
98
+ ### Cutting a release
99
+
100
+ `CHANGELOG.md` is generated from git history by git-cliff and rewritten whole
101
+ on every release, so an entry is only ever as good as the commit subject it
102
+ came from — write the subject for the person reading the changelog. Non-`feat`,
103
+ non-`fix` types are grouped or skipped per `cliff.toml`. `task
104
+ release:changelog:preview` shows what the next release would say.
105
+
106
+ ```sh
107
+ task release:prepare # menu: auto | patch | minor | major
108
+ task release:prepare BUMP=minor # or name the level outright
109
+ ```
110
+
111
+ `auto` takes the level from those same subjects (`cliff.toml`'s `[bump]` rules:
112
+ a feat is a minor bump, a breaking change is not yet a major one). It is a
113
+ second opinion — a subject that undersells its change bumps too little, and the
114
+ other three levels are for saying so.
115
+
116
+ The task writes the version into `package.json` and the lockfile, regenerates
117
+ `CHANGELOG.md` under the new tag, and runs `task check`. Nothing is committed:
118
+ review the diff, then run the three commands it prints. Pushing the `v*` tag is
119
+ what publishes — see `.github/workflows/release.yml`.
120
+
121
+ `task release:changelog` regenerates the file without releasing, which is what
122
+ a commit landing between releases needs.
123
+
98
124
  The docs site is a separate toolchain and deliberately not part of `check` —
99
125
  `check` must stay runnable with no Python present. `task docs:serve` previews
100
126
  it, `task docs:build` is the strict build the `pages` workflow gates PRs with.
@@ -1 +1 @@
1
- {"version":3,"file":"ratings.d.ts","sourceRoot":"","sources":["../../src/cli/ratings.ts"],"names":[],"mappings":"AAYA,OAAO,EAAkB,KAAK,QAAQ,EAAE,MAAM,WAAW,CAAC;AAoF1D,wBAAsB,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,CAAC,CAwE7D"}
1
+ {"version":3,"file":"ratings.d.ts","sourceRoot":"","sources":["../../src/cli/ratings.ts"],"names":[],"mappings":"AAYA,OAAO,EAAkB,KAAK,QAAQ,EAAE,MAAM,WAAW,CAAC;AAsG1D,wBAAsB,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,CAAC,CAgF7D"}
@@ -50,6 +50,24 @@ function forgeEnv(provider) {
50
50
  }
51
51
  return { api: api ?? "", project, token };
52
52
  }
53
+ /**
54
+ * The forge host to publish as `providers.rating_host`, derived from the
55
+ * GraphQL endpoint this run actually talked to.
56
+ *
57
+ * Derived rather than configured, so it can never disagree with where the
58
+ * threads were created — and derived per run, so an operator who moves the
59
+ * instance publishes the new host without touching `index.config.json`.
60
+ * `undefined` for an endpoint that does not parse, which drops the key
61
+ * rather than publishing a value no consumer could dial.
62
+ */
63
+ function ratingHost(api) {
64
+ try {
65
+ return new URL(api).host || undefined;
66
+ }
67
+ catch {
68
+ return undefined;
69
+ }
70
+ }
53
71
  /** Every ref in `index/`. The tally runs before `build`, so `all.json` does not exist yet. */
54
72
  function desiredRefs(root, findMetadataFiles) {
55
73
  const refs = [];
@@ -129,7 +147,17 @@ export async function ratings(root) {
129
147
  }
130
148
  for (const conflict of result.conflicts)
131
149
  console.error(conflictWarning(conflict));
132
- const doc = statsDocument(mergeStats(seed, "rating", cfg.provider, result.fresh));
150
+ const merged = mergeStats(seed, "rating", cfg.provider, result.fresh);
151
+ // Replaced or dropped every run, never carried: a seed's `rating_host` is a
152
+ // claim about an endpoint this run may no longer be using, and a consumer
153
+ // sends a credential to it.
154
+ const providers = { ...merged.providers };
155
+ const host = ratingHost(forge.api);
156
+ if (host === undefined)
157
+ delete providers.rating_host;
158
+ else
159
+ providers.rating_host = host;
160
+ const doc = statsDocument({ ...merged, providers });
133
161
  fs.writeFileSync(path.join(rootDir, STATS_FILE), JSON.stringify(doc, null, 2) + "\n");
134
162
  console.log(logLine(result));
135
163
  return EXIT.ok;
@@ -1 +1 @@
1
- {"version":3,"file":"ratings.js","sourceRoot":"","sources":["../../src/cli/ratings.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"ratings.js","sourceRoot":"","sources":["../../src/cli/ratings.ts"],"names":[],"mappings":"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"}
@@ -48,6 +48,7 @@ const { choices, label, title } = Astro.props;
48
48
  aria-checked={i === 0 ? "true" : "false"}
49
49
  data-os-pick={choice.command}
50
50
  data-os-name={choice.name}
51
+ data-os-href={choice.href}
51
52
  >
52
53
  {choice.path ? <BrandMark path={choice.path} /> : choice.Icon && <choice.Icon size={16} />}
53
54
  <span>{choice.name}</span>
@@ -104,6 +104,12 @@ const { title, description, image = config.logo ?? config.favicon } = Astro.prop
104
104
  for (const glyph of bar.querySelectorAll("[data-os-glyph]")) {
105
105
  glyph.hidden = glyph.dataset.osGlyph !== pick.dataset.osName;
106
106
  }
107
+ // The action segment follows the choice where it has one to follow:
108
+ // the registry bar's VS Code link carries the scope the command names,
109
+ // so picking Global cannot leave the button writing project config.
110
+ // A choice with no href leaves the link alone rather than blanking it.
111
+ const link = bar.querySelector("[data-os-link]");
112
+ if (link && pick.dataset.osHref) link.href = pick.dataset.osHref;
107
113
  };
108
114
 
109
115
  // The platform a bar should start on, or null for one this visitor's
@@ -87,6 +87,13 @@ export declare function vscodeVoteUrl(extension: string | null, ref: string): st
87
87
  * `vscode://<publisher.extension>/add-registry?index=<url>&alias=<name>` — the
88
88
  * one-click counterpart of the `grim config registry add` line beside it.
89
89
  *
90
+ * `scope` carries the Scope picker sitting in the same bar, so the button
91
+ * writes where the copyable command says it would. The extension clamps it
92
+ * rather than trusting it — with no folder open the write is global whatever
93
+ * the link asked for — and words its confirmation modal off the RESULT, so a
94
+ * page steering a machine-wide write is what the user reads before agreeing.
95
+ * Omitting it leaves the extension on its own host-derived default.
96
+ *
90
97
  * Null unless the link would actually work: the handler takes https only (an
91
98
  * index locator is fetched with whatever credentials the user configures for
92
99
  * it), refuses embedded credentials, and caps the URL at 2048 characters.
@@ -96,7 +103,7 @@ export declare function vscodeVoteUrl(extension: string | null, ref: string): st
96
103
  export declare function addRegistryUrl(extension: string | null, registry: {
97
104
  alias: string;
98
105
  index: string;
99
- } | null): string | null;
106
+ } | null, scope?: "global" | "project"): string | null;
100
107
  /**
101
108
  * A bundle member's `id` resolved to the reference the index stores.
102
109
  *
@@ -183,13 +183,20 @@ const REGISTRY_ALIAS = /^[A-Za-z0-9][A-Za-z0-9_-]{0,31}$/;
183
183
  * `vscode://<publisher.extension>/add-registry?index=<url>&alias=<name>` — the
184
184
  * one-click counterpart of the `grim config registry add` line beside it.
185
185
  *
186
+ * `scope` carries the Scope picker sitting in the same bar, so the button
187
+ * writes where the copyable command says it would. The extension clamps it
188
+ * rather than trusting it — with no folder open the write is global whatever
189
+ * the link asked for — and words its confirmation modal off the RESULT, so a
190
+ * page steering a machine-wide write is what the user reads before agreeing.
191
+ * Omitting it leaves the extension on its own host-derived default.
192
+ *
186
193
  * Null unless the link would actually work: the handler takes https only (an
187
194
  * index locator is fetched with whatever credentials the user configures for
188
195
  * it), refuses embedded credentials, and caps the URL at 2048 characters.
189
196
  * Checking here keeps a broken button off the page rather than putting the
190
197
  * failure in the user's hands.
191
198
  */
192
- export function addRegistryUrl(extension, registry) {
199
+ export function addRegistryUrl(extension, registry, scope) {
193
200
  if (!extension || !registry || !REGISTRY_ALIAS.test(registry.alias))
194
201
  return null;
195
202
  let url;
@@ -204,6 +211,8 @@ export function addRegistryUrl(extension, registry) {
204
211
  if (url.href.length > 2048)
205
212
  return null;
206
213
  const query = new URLSearchParams({ index: url.href, alias: registry.alias });
214
+ if (scope)
215
+ query.set("scope", scope);
207
216
  return `vscode://${extension}/add-registry?${query.toString()}`;
208
217
  }
209
218
  /**
@@ -238,6 +238,13 @@ const REGISTRY_ALIAS = /^[A-Za-z0-9][A-Za-z0-9_-]{0,31}$/;
238
238
  * `vscode://<publisher.extension>/add-registry?index=<url>&alias=<name>` — the
239
239
  * one-click counterpart of the `grim config registry add` line beside it.
240
240
  *
241
+ * `scope` carries the Scope picker sitting in the same bar, so the button
242
+ * writes where the copyable command says it would. The extension clamps it
243
+ * rather than trusting it — with no folder open the write is global whatever
244
+ * the link asked for — and words its confirmation modal off the RESULT, so a
245
+ * page steering a machine-wide write is what the user reads before agreeing.
246
+ * Omitting it leaves the extension on its own host-derived default.
247
+ *
241
248
  * Null unless the link would actually work: the handler takes https only (an
242
249
  * index locator is fetched with whatever credentials the user configures for
243
250
  * it), refuses embedded credentials, and caps the URL at 2048 characters.
@@ -247,6 +254,7 @@ const REGISTRY_ALIAS = /^[A-Za-z0-9][A-Za-z0-9_-]{0,31}$/;
247
254
  export function addRegistryUrl(
248
255
  extension: string | null,
249
256
  registry: { alias: string; index: string } | null,
257
+ scope?: "global" | "project",
250
258
  ): string | null {
251
259
  if (!extension || !registry || !REGISTRY_ALIAS.test(registry.alias)) return null;
252
260
  let url: URL;
@@ -258,6 +266,7 @@ export function addRegistryUrl(
258
266
  if (url.protocol !== "https:" || url.username !== "" || url.password !== "") return null;
259
267
  if (url.href.length > 2048) return null;
260
268
  const query = new URLSearchParams({ index: url.href, alias: registry.alias });
269
+ if (scope) query.set("scope", scope);
261
270
  return `vscode://${extension}/add-registry?${query.toString()}`;
262
271
  }
263
272
 
@@ -20,6 +20,13 @@ export interface Choice {
20
20
  Icon?: (props: {
21
21
  size?: number;
22
22
  }) => unknown;
23
+ /**
24
+ * Where the bar's action segment should point once this choice is picked.
25
+ * Only the registry bar has one — a deep link that has to agree with the
26
+ * scope the command beside it names — and a bar whose choices carry none
27
+ * leaves its action link exactly as rendered.
28
+ */
29
+ href?: string;
23
30
  }
24
31
  /**
25
32
  * The installer choices, one per platform rather than one per config row.
@@ -14,6 +14,7 @@
14
14
  // <CommandBar choices={installChoices(data.config)} noun="install command" … />
15
15
  import { FolderRoot, Globe } from "lucide-preact";
16
16
  import { mdiApple, mdiConsole, mdiMicrosoftWindows, mdiPenguin, } from "@mdi/js";
17
+ import { addRegistryUrl } from "./catalog.js";
17
18
  /**
18
19
  * `mdiPenguin`, not `mdiLinux`: Tux's own silhouette is a solid mass that
19
20
  * reads as an indistinct blob at 16px, where the plainer penguin keeps its
@@ -74,9 +75,15 @@ export function registryScopeChoices(config, registry = config.registry) {
74
75
  const add = registryAddCommand(config, registry);
75
76
  if (!add)
76
77
  return [];
78
+ const link = (scope) => addRegistryUrl(config.vscodeExtension, registry, scope) ?? undefined;
77
79
  return [
78
- { name: "Global", command: `grim --global ${add.slice("grim ".length)}`, Icon: Globe },
79
- { name: "Project", command: add, Icon: FolderRoot },
80
+ {
81
+ name: "Global",
82
+ command: `grim --global ${add.slice("grim ".length)}`,
83
+ Icon: Globe,
84
+ href: link("global"),
85
+ },
86
+ { name: "Project", command: add, Icon: FolderRoot, href: link("project") },
80
87
  ];
81
88
  }
82
89
  /**
@@ -21,6 +21,7 @@ import {
21
21
  mdiPenguin,
22
22
  } from "@mdi/js";
23
23
  import type { RegistryHint, ResolvedSiteConfig } from "../../../config.js";
24
+ import { addRegistryUrl } from "./catalog.js";
24
25
 
25
26
  // Re-exported so an index's own page can name the shape it passes to the two
26
27
  // functions below without reaching past `@grim/lib/*` into the package's
@@ -45,6 +46,13 @@ export interface Choice {
45
46
  path?: string;
46
47
  /** Lucide component, taking a `size` prop. */
47
48
  Icon?: (props: { size?: number }) => unknown;
49
+ /**
50
+ * Where the bar's action segment should point once this choice is picked.
51
+ * Only the registry bar has one — a deep link that has to agree with the
52
+ * scope the command beside it names — and a bar whose choices carry none
53
+ * leaves its action link exactly as rendered.
54
+ */
55
+ href?: string;
48
56
  }
49
57
 
50
58
  /**
@@ -115,9 +123,16 @@ export function registryScopeChoices(
115
123
  ): Choice[] {
116
124
  const add = registryAddCommand(config, registry);
117
125
  if (!add) return [];
126
+ const link = (scope: "global" | "project") =>
127
+ addRegistryUrl(config.vscodeExtension, registry, scope) ?? undefined;
118
128
  return [
119
- { name: "Global", command: `grim --global ${add.slice("grim ".length)}`, Icon: Globe },
120
- { name: "Project", command: add, Icon: FolderRoot },
129
+ {
130
+ name: "Global",
131
+ command: `grim --global ${add.slice("grim ".length)}`,
132
+ Icon: Globe,
133
+ href: link("global"),
134
+ },
135
+ { name: "Project", command: add, Icon: FolderRoot, href: link("project") },
121
136
  ];
122
137
  }
123
138
 
@@ -14,7 +14,11 @@ const { config, packages } = data;
14
14
  // One-click counterpart of the `grim config registry add` line: the VS Code
15
15
  // extension registers a URI handler that writes the registry after a modal
16
16
  // confirmation. Null whenever the link would not work — see `addRegistryUrl`.
17
- const addRegistry = addRegistryUrl(config.vscodeExtension, config.registry);
17
+ //
18
+ // Scoped `global` because that is the choice the picker beside it starts on;
19
+ // picking Project rewrites this href from `data-os-href` (see `__grimPick`),
20
+ // so the two halves of the bar never disagree about where the write lands.
21
+ const addRegistry = addRegistryUrl(config.vscodeExtension, config.registry, "global");
18
22
 
19
23
  // The extension's own marketplace page — the one thing on this page that
20
24
  // helps a visitor who does not have it installed yet.
@@ -91,6 +95,7 @@ const scopeChoices = registryScopeChoices(config);
91
95
  <a
92
96
  slot="action"
93
97
  class="seg brand-link"
98
+ data-os-link
94
99
  href={addRegistry}
95
100
  title="Add this index via VS Code"
96
101
  aria-label="Add this index via VS Code"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@grimoire-rs/indexer",
3
- "version": "0.5.3",
3
+ "version": "0.5.4",
4
4
  "description": "CLI + Astro integration for running your own grim package index",
5
5
  "type": "module",
6
6
  "license": "Apache-2.0",