@inerrata-corporation/errata 2.0.21-dev.4001 → 2.0.21-dev.4004
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/errata.mjs +1 -1
- package/package.json +1 -1
- package/resources/claude-mods/errata-minimap/.claude-plugin/plugin.json +1 -1
- package/resources/claude-mods/errata-minimap/hooks/bookcase.ts +37 -8
- package/resources/claude-mods/errata-minimap/hooks/chains.ts +29 -18
- package/resources/claude-mods/errata-minimap/hooks/register.tsx +85 -6
- package/resources/context-plugin/.codex-plugin/plugin.json +1 -1
- package/resources/context-plugin/SHA256SUMS +1 -1
package/errata.mjs
CHANGED
|
@@ -83973,7 +83973,7 @@ function createWatchBreaker(deps) {
|
|
|
83973
83973
|
}
|
|
83974
83974
|
|
|
83975
83975
|
// src/engine.ts
|
|
83976
|
-
var DAEMON_VERSION = true ? "2.0.21-dev.
|
|
83976
|
+
var DAEMON_VERSION = true ? "2.0.21-dev.4004" : "2.0.0-alpha.0";
|
|
83977
83977
|
var IGNORED_PATH = /[\\/](?:\.git|node_modules|\.errata|\.claude|\.codex|\.cursor|\.turbo|\.next|dist|coverage|test-results|playwright-report|__pycache__)(?:[\\/]|$)/;
|
|
83978
83978
|
var IGNORED_NOISE = /castalia\.db|eventlog\.sqlite|turn-cursors/;
|
|
83979
83979
|
var GIT_OP_MUTE_MS = 4e3;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "errata-minimap",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"author": { "name": "inErrata", "url": "https://inerrata.ai" },
|
|
5
5
|
"description": "The errata library: the causal chains of the latest burst that entered the agent's context, in bookcases by technology with a catalog card — and errata's inline tags, tidied in the chat.",
|
|
6
6
|
"types": "./types/index.d.ts"
|
|
@@ -126,7 +126,7 @@ const SPACE = 0x20
|
|
|
126
126
|
const TALL_SHELF = 4
|
|
127
127
|
const SHORT_SHELF = 3
|
|
128
128
|
/** Cells a shelf keeps after its books for its plate. */
|
|
129
|
-
const PLATE =
|
|
129
|
+
const PLATE = 17
|
|
130
130
|
/** Rows the catalog card never gives up: header, two lines of chain, footer. */
|
|
131
131
|
const MIN_CARD = 4
|
|
132
132
|
const MAX_CARD = 8
|
|
@@ -157,7 +157,7 @@ function placeCases(catalog: Catalog, columns: number, rows: number, zone: numbe
|
|
|
157
157
|
let tierHeight = 0
|
|
158
158
|
for (const bookcase of catalog.cases) {
|
|
159
159
|
const fullest = Math.max(...bookcase.shelves.map(s => s.books.reduce((w, b) => w + widthOf(b), 0)))
|
|
160
|
-
// Room after the fullest shelf for its plate (" 1
|
|
160
|
+
// Room after the fullest shelf for its plate (" 1 loose volumes "), and on the cornice for the tech.
|
|
161
161
|
const width = Math.min(columns, Math.max(bookcase.tech.length + 6, fullest + 4 + PLATE))
|
|
162
162
|
if (x > 0 && x + width > columns) {
|
|
163
163
|
x = 0
|
|
@@ -185,6 +185,40 @@ function placeCases(catalog: Catalog, columns: number, rows: number, zone: numbe
|
|
|
185
185
|
return placed
|
|
186
186
|
}
|
|
187
187
|
|
|
188
|
+
/** The library's geometry: the card it keeps, the shelf height, where every case stands. */
|
|
189
|
+
function layoutLibrary(catalog: Catalog, columns: number, rows: number, focusId?: string) {
|
|
190
|
+
const focus = focusBook(catalog, focusId)
|
|
191
|
+
// The card asks for its chain in full (header, every wrapped line, footer); the cases get the rest, at least a third.
|
|
192
|
+
const need = focus ? 2 + partsOf(focus).reduce((n, [, part]) => n + wrap(part.name, columns - 5).length, 0) : 0
|
|
193
|
+
const card = Math.max(MIN_CARD, Math.min(need, Math.max(MAX_CARD, Math.round(rows * 0.6)), rows - Math.ceil(rows / 3)))
|
|
194
|
+
const zone = rows - card >= 1 + TALL_SHELF + 1 ? TALL_SHELF : SHORT_SHELF
|
|
195
|
+
return { focus, need, card, zone, placed: placeCases(catalog, columns, rows - card, zone) }
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/** Where a drawn book stands, in cells from the library's top-left: its spine's whole zone above the plank. */
|
|
199
|
+
export type BookSpot = { book: Book; x: number; top: number; width: number; height: number }
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Every drawn book's spot, from the same layout `rasterizeLibrary` draws: the
|
|
203
|
+
* hover and click targets laid over the spines, so they can never disagree
|
|
204
|
+
* with the picture about where a book is.
|
|
205
|
+
*/
|
|
206
|
+
export function librarySpots(catalog: Catalog, columns: number, rows: number, focusId?: string): BookSpot[] {
|
|
207
|
+
const { zone, placed } = layoutLibrary(catalog, columns, rows, focusId)
|
|
208
|
+
const spots: BookSpot[] = []
|
|
209
|
+
for (const c of placed) {
|
|
210
|
+
c.shelves.forEach(({ books }, si) => {
|
|
211
|
+
const plank = c.y + 1 + si * (zone + 1) + zone
|
|
212
|
+
let x = c.x + 2
|
|
213
|
+
for (const book of books) {
|
|
214
|
+
spots.push({ book, x, top: plank - zone, width: widthOf(book), height: zone })
|
|
215
|
+
x += widthOf(book)
|
|
216
|
+
}
|
|
217
|
+
})
|
|
218
|
+
}
|
|
219
|
+
return spots
|
|
220
|
+
}
|
|
221
|
+
|
|
188
222
|
/** Every book drawn, in reading order: the order the reveal raises them. */
|
|
189
223
|
function drawnBooks(placed: Placed[]): Book[] {
|
|
190
224
|
return placed.flatMap(p => p.shelves.flatMap(s => s.books))
|
|
@@ -220,12 +254,7 @@ export function rasterizeLibrary(catalog: Catalog, columns: number, rows: number
|
|
|
220
254
|
return x + k
|
|
221
255
|
}
|
|
222
256
|
|
|
223
|
-
const focus =
|
|
224
|
-
// The card asks for its chain in full (header, every wrapped line, footer); the cases get the rest, at least a third.
|
|
225
|
-
const need = focus ? 2 + partsOf(focus).reduce((n, [, part]) => n + wrap(part.name, columns - 5).length, 0) : 0
|
|
226
|
-
const card = Math.max(MIN_CARD, Math.min(need, Math.max(MAX_CARD, Math.round(rows * 0.6)), rows - Math.ceil(rows / 3)))
|
|
227
|
-
const zone = rows - card >= 1 + TALL_SHELF + 1 ? TALL_SHELF : SHORT_SHELF
|
|
228
|
-
const placed = placeCases(catalog, columns, rows - card, zone)
|
|
257
|
+
const { focus, need, card, zone, placed } = layoutLibrary(catalog, columns, rows, options.focus)
|
|
229
258
|
const order = drawnBooks(placed)
|
|
230
259
|
const pulled = bookHolding(catalog, highlight)
|
|
231
260
|
const scores = catalog.books.map(b => b.score).sort((a, b) => a - b)
|
|
@@ -37,7 +37,6 @@ export type Book = {
|
|
|
37
37
|
memberIds: string[]
|
|
38
38
|
tech?: string
|
|
39
39
|
community?: string
|
|
40
|
-
communityLabel?: string | null
|
|
41
40
|
patterns: string[]
|
|
42
41
|
anchor?: string
|
|
43
42
|
/** Its `errata_neighbors` answer has arrived. */
|
|
@@ -247,10 +246,7 @@ export function catalogue(graph: MapGraph, facts: Record<string, ChainFacts | nu
|
|
|
247
246
|
}
|
|
248
247
|
// Only cloud-merged rows carry a community; theirs is the cloud partition's, kept apart from the local one.
|
|
249
248
|
const cloud = book.community ? undefined : graph.nodes.find(n => book.memberIds.includes(n.id) && n.community)?.community
|
|
250
|
-
if (cloud) {
|
|
251
|
-
book.community = `cloud_${cloud.id}`
|
|
252
|
-
book.communityLabel = cloud.label
|
|
253
|
-
}
|
|
249
|
+
if (cloud) book.community = `cloud_${cloud.id}`
|
|
254
250
|
book.anchor = anchorOf(f, book.problem?.name)
|
|
255
251
|
candidates.set(book.key, techs.filter((t, i) => techs.findIndex(u => u.name === t.name) === i))
|
|
256
252
|
}
|
|
@@ -261,8 +257,10 @@ export function catalogue(graph: MapGraph, facts: Record<string, ChainFacts | nu
|
|
|
261
257
|
}
|
|
262
258
|
|
|
263
259
|
const GENERAL = 'general'
|
|
264
|
-
/** A shelf holds a community only when two of its books are in view
|
|
260
|
+
/** A shelf holds a community, or a shared Pattern, only when two of its books are in view. */
|
|
265
261
|
const MIN_SHELF = 2
|
|
262
|
+
/** The shelf for books that share neither with another book in view. */
|
|
263
|
+
export const LOOSE = 'loose volumes'
|
|
266
264
|
|
|
267
265
|
function bookcases(books: Book[]): Bookcase[] {
|
|
268
266
|
const byTech = new Map<string, Book[]>()
|
|
@@ -272,31 +270,44 @@ function bookcases(books: Book[]): Bookcase[] {
|
|
|
272
270
|
.map(([tech, held]) => ({ tech, code: classMark(tech), shelves: shelves(held), isGeneral: tech === GENERAL }))
|
|
273
271
|
}
|
|
274
272
|
|
|
273
|
+
/**
|
|
274
|
+
* Shelves in three tiers: a community with two books in view; then, among the
|
|
275
|
+
* books left, a Pattern two of them instance (agent-named shapes are often the
|
|
276
|
+
* truest grouping there is); then the loose volumes. A burst of ten chains
|
|
277
|
+
* rarely holds two from one community — they are small — so community alone
|
|
278
|
+
* put nearly every book on one catch-all shelf.
|
|
279
|
+
*/
|
|
275
280
|
function shelves(books: Book[]): Shelf[] {
|
|
281
|
+
const out: Shelf[] = []
|
|
276
282
|
const byCommunity = new Map<string, Book[]>()
|
|
277
283
|
for (const b of books) if (b.community) byCommunity.set(b.community, [...(byCommunity.get(b.community) ?? []), b])
|
|
278
|
-
const out: Shelf[] = []
|
|
279
284
|
for (const [key, held] of byCommunity) {
|
|
280
|
-
if (held.length
|
|
281
|
-
|
|
285
|
+
if (held.length >= MIN_SHELF) out.push({ key, name: shelfName(held), number: 0, books: held, isMiscellany: false })
|
|
286
|
+
}
|
|
287
|
+
let rest = books.filter(b => !out.some(s => s.books.includes(b)))
|
|
288
|
+
// The most-shared Pattern first, so a book that instances several joins the biggest group.
|
|
289
|
+
for (;;) {
|
|
290
|
+
const count = new Map<string, Book[]>()
|
|
291
|
+
for (const b of rest) for (const p of new Set(b.patterns)) count.set(p, [...(count.get(p) ?? []), b])
|
|
292
|
+
const best = [...count].sort((a, b) => b[1].length - a[1].length || a[0].localeCompare(b[0]))[0]
|
|
293
|
+
if (!best || best[1].length < MIN_SHELF) break
|
|
294
|
+
out.push({ key: `pattern:${best[0]}`, name: patternTitle(best[0]), number: 0, books: best[1], isMiscellany: false })
|
|
295
|
+
rest = rest.filter(b => !best[1].includes(b))
|
|
282
296
|
}
|
|
283
|
-
|
|
284
|
-
const rest = books.filter(b => !shelved.has(b))
|
|
285
|
-
if (rest.length > 0) out.push({ key: 'miscellany', name: 'miscellany', number: 0, books: rest, isMiscellany: true })
|
|
297
|
+
if (rest.length > 0) out.push({ key: 'loose', name: LOOSE, number: 0, books: rest, isMiscellany: true })
|
|
286
298
|
out.forEach((s, i) => (s.number = i + 1))
|
|
287
299
|
return out
|
|
288
300
|
}
|
|
289
301
|
|
|
290
302
|
/**
|
|
291
303
|
* A community has no name of its own (the local partition's label is null), so
|
|
292
|
-
* the shelf is named from what its books share: the
|
|
293
|
-
*
|
|
294
|
-
*
|
|
295
|
-
*
|
|
304
|
+
* the shelf is named from what its books share: the Pattern most of its books
|
|
305
|
+
* are INSTANCE_OF — the graph's own generalisation of what those problems have
|
|
306
|
+
* in common — else its strongest problem, the shelf's exemplar. Never the cloud
|
|
307
|
+
* community's theme label: those can describe other members entirely (#43
|
|
308
|
+
* "Canvas zoom/scroll…" held SQLite busy_timeout problems).
|
|
296
309
|
*/
|
|
297
310
|
export function shelfName(books: Book[]): string {
|
|
298
|
-
const label = books.find(b => b.communityLabel)?.communityLabel
|
|
299
|
-
if (label) return label
|
|
300
311
|
const count = new Map<string, number>()
|
|
301
312
|
for (const b of books) for (const p of new Set(b.patterns)) count.set(p, (count.get(p) ?? 0) + 1)
|
|
302
313
|
if (count.size > 0) return patternTitle([...count].reduce((a, b) => (b[1] > a[1] ? b : a))[0])
|
|
@@ -2,7 +2,7 @@ import { atom, read, update } from 'claude-code'
|
|
|
2
2
|
import type { EngineInterface, Register, Timer } from 'claude-code'
|
|
3
3
|
|
|
4
4
|
import type { Burst, ChainFacts, MapGraph, Verdict } from '../types'
|
|
5
|
-
import { BINDING_GLYPH, BINDING_NAME, bindingOf, fitTitle, focusBook, PALETTE, rasterizeLibrary } from './bookcase'
|
|
5
|
+
import { BINDING_GLYPH, BINDING_NAME, bindingOf, fitTitle, focusBook, librarySpots, PALETTE, rasterizeLibrary } from './bookcase'
|
|
6
6
|
import type { Binding, LibraryFrame, LibraryOptions, Room } from './bookcase'
|
|
7
7
|
import { bookHolding, callNumber, catalogue, enrichTargets, parseNeighbors } from './chains'
|
|
8
8
|
import type { Catalog } from './chains'
|
|
@@ -84,6 +84,10 @@ const PANE_VIEW_KEY = 'paneView'
|
|
|
84
84
|
|
|
85
85
|
/** Rows an inline pane asks for: room for a tier of bookcases and the card without burying the transcript. */
|
|
86
86
|
const INLINE_ROWS = 20
|
|
87
|
+
/** Rows on a portrait terminal: the header, one tier of shelves, a short card. */
|
|
88
|
+
const COMPACT_ROWS = 11
|
|
89
|
+
/** The terminal's own colour in a raster cell (bit 24 alone). */
|
|
90
|
+
const DEFAULT_CELL = 0x01000000
|
|
87
91
|
|
|
88
92
|
const GROW_MS = 900
|
|
89
93
|
const TYPE_MS = 500
|
|
@@ -191,6 +195,8 @@ async function animate($: EngineInterface): Promise<void> {
|
|
|
191
195
|
if (!isRevealing && !motion.highlight && !isMarking) {
|
|
192
196
|
motion.timer?.cancel()
|
|
193
197
|
motion.timer = undefined
|
|
198
|
+
// At rest again: redraw once so the books' hover and click targets are laid (they stay off while blits run).
|
|
199
|
+
if (pane.view === 'books') $.ui.invalidate('ui.render')
|
|
194
200
|
}
|
|
195
201
|
}
|
|
196
202
|
|
|
@@ -522,8 +528,15 @@ export const register: Register = on => {
|
|
|
522
528
|
const { Box, Text } = $.ui.resolve(e)
|
|
523
529
|
const width = Math.max(24, e.props.bodyColumns)
|
|
524
530
|
// Docked, the library takes the screen's height; inline it sits above the prompt and keeps to its row budget.
|
|
531
|
+
// A portrait terminal (taller than wide, at ~2 rows per column of width) can't dock and has the least
|
|
532
|
+
// transcript to spare, so inline there the library is compact: one tier of shelves and a short card.
|
|
525
533
|
const screenRows = e.viewport?.rows ?? 30
|
|
526
|
-
const
|
|
534
|
+
const isPortrait = e.props.placement === 'inline' && !!e.viewport && e.viewport.rows * 2 > e.viewport.columns
|
|
535
|
+
const height = isPortrait
|
|
536
|
+
? Math.max(10, Math.min(COMPACT_ROWS, screenRows - 6))
|
|
537
|
+
: e.props.placement === 'inline'
|
|
538
|
+
? Math.max(12, Math.min(INLINE_ROWS, screenRows - 10))
|
|
539
|
+
: Math.max(12, screenRows - 4)
|
|
527
540
|
const map = await read($, graph)
|
|
528
541
|
const hex = (color: number) => `#${color.toString(16).padStart(6, '0')}`
|
|
529
542
|
|
|
@@ -611,7 +624,8 @@ export const register: Register = on => {
|
|
|
611
624
|
const now = await $.clock.now()
|
|
612
625
|
// A verdict on a prior not on the shelves has no plank to be stamped on: it gets a line.
|
|
613
626
|
const unshelved = (await read($, verdicts)).filter(v => now - v.at < VERDICT_SHOWN_MS && !bookHolding(catalog, v.id)).slice(0, 2)
|
|
614
|
-
const
|
|
627
|
+
const showLegend = legend.length > 0 && !isPortrait
|
|
628
|
+
const libraryRows = Math.max(8, height - 1 - (showLegend ? 1 : 0) - unshelved.length)
|
|
615
629
|
// Blits must match the mounted size: remember what this pass mounts.
|
|
616
630
|
motion.raster = { columns: width, rows: libraryRows }
|
|
617
631
|
const options = await libraryOptions($, map)
|
|
@@ -625,8 +639,73 @@ export const register: Register = on => {
|
|
|
625
639
|
</Box>
|
|
626
640
|
) : e.surface === 'terminal' ? (
|
|
627
641
|
(() => {
|
|
628
|
-
const { Raster } = $.ui.resolve(e)
|
|
629
|
-
|
|
642
|
+
const { Raster, Button } = $.ui.resolve(e)
|
|
643
|
+
const cells = rasterizeLibrary(catalog, width, libraryRows, options)
|
|
644
|
+
// Hover and click ride on positioned replicas of each spine (same glyphs, same colours) laid
|
|
645
|
+
// over the raster, since pointer feedback attaches to elements, not cells. Laid only at rest:
|
|
646
|
+
// a reveal, a burst highlight or a flashing stamp repaints the raster by blit, which a static
|
|
647
|
+
// replica would hide.
|
|
648
|
+
const isAtRest = motion.frame.type >= 1 && !motion.highlight && Object.keys(motion.marks).length === 0
|
|
649
|
+
const spots = isAtRest ? librarySpots(catalog, width, libraryRows, options.focus) : []
|
|
650
|
+
const grid = new Uint32Array(Uint8Array.fromBase64(cells).buffer)
|
|
651
|
+
const cell = (x: number, y: number) => {
|
|
652
|
+
const i = (y * width + x) * 3
|
|
653
|
+
return { ch: String.fromCodePoint(grid[i]!), fg: grid[i + 1]!, bg: grid[i + 2]! }
|
|
654
|
+
}
|
|
655
|
+
const colourOf = (c: number) => (c === DEFAULT_CELL ? undefined : hex(c))
|
|
656
|
+
const cardWidth = Math.min(46, width - 2)
|
|
657
|
+
return (
|
|
658
|
+
<Box key="library" position="relative" width={width} height={libraryRows}>
|
|
659
|
+
<Raster key="map" columns={width} rows={libraryRows} cells={cells} />
|
|
660
|
+
{spots.map(s => {
|
|
661
|
+
const scope = `book:${s.book.key}`.slice(0, 64)
|
|
662
|
+
const parts = (['cause', 'problem', 'fix'] as const).flatMap(r => (s.book[r] ? [s.book[r]!] : []))
|
|
663
|
+
const cardHeight = parts.length + 3
|
|
664
|
+
const cardLeft = s.x + s.width + 1 + cardWidth <= width ? s.x + s.width + 1 : Math.max(0, s.x - cardWidth - 1)
|
|
665
|
+
const cardTop = Math.max(0, Math.min(s.top, libraryRows - cardHeight))
|
|
666
|
+
const pin = () => {
|
|
667
|
+
motion.focus = s.book.problem?.id ?? s.book.memberIds[0]
|
|
668
|
+
$.ui.invalidate('ui.render')
|
|
669
|
+
}
|
|
670
|
+
return [
|
|
671
|
+
...[...Array(s.height).keys()].map(r => (
|
|
672
|
+
<Box key={`${scope}:${r}`} position="absolute" top={s.top + r} left={s.x} hover={{ scope }}>
|
|
673
|
+
<Button key={`${scope}:press:${r}`} plain onPress={pin}>
|
|
674
|
+
{[...Array(s.width).keys()].map(dx => {
|
|
675
|
+
const c = cell(s.x + dx, s.top + r)
|
|
676
|
+
return <Text color={colourOf(c.fg)} backgroundColor={colourOf(c.bg)}>{c.ch}</Text>
|
|
677
|
+
})}
|
|
678
|
+
</Button>
|
|
679
|
+
</Box>
|
|
680
|
+
)),
|
|
681
|
+
// The hover card: what the book holds, revealed while the pointer is anywhere on its spine.
|
|
682
|
+
<Box
|
|
683
|
+
key={`${scope}:card`}
|
|
684
|
+
position="absolute"
|
|
685
|
+
top={cardTop}
|
|
686
|
+
left={cardLeft}
|
|
687
|
+
width={cardWidth}
|
|
688
|
+
display="none"
|
|
689
|
+
hover={{ scope, display: 'flex' }}
|
|
690
|
+
flexDirection="column"
|
|
691
|
+
borderStyle="round"
|
|
692
|
+
borderColor={hex(palette.gilt)}
|
|
693
|
+
>
|
|
694
|
+
<Text bold wrap="truncate-end" color={hex(palette.binding.problem)}>
|
|
695
|
+
{callNumber(catalog, s.book)} <Text dimColor>{s.book.tech ?? 'general'}</Text>
|
|
696
|
+
</Text>
|
|
697
|
+
{parts.map(part => (
|
|
698
|
+
<Text wrap="truncate-end">
|
|
699
|
+
<Text color={hex(palette.binding[bindingOf(part.label)])}>{BINDING_GLYPH[bindingOf(part.label)]} </Text>
|
|
700
|
+
<Text dimColor={!part.isDelivered}>{fitTitle(part.name, cardWidth - 6)}</Text>
|
|
701
|
+
</Text>
|
|
702
|
+
))}
|
|
703
|
+
<Text dimColor wrap="truncate-end">click to read it on the card</Text>
|
|
704
|
+
</Box>,
|
|
705
|
+
]
|
|
706
|
+
})}
|
|
707
|
+
</Box>
|
|
708
|
+
)
|
|
630
709
|
})()
|
|
631
710
|
) : (
|
|
632
711
|
// No cell grid off the terminal: the catalog as a reading list, the book in focus first.
|
|
@@ -656,7 +735,7 @@ export const register: Register = on => {
|
|
|
656
735
|
</Text>
|
|
657
736
|
</Text>
|
|
658
737
|
{library}
|
|
659
|
-
{
|
|
738
|
+
{showLegend && (
|
|
660
739
|
<Text wrap="truncate-end">
|
|
661
740
|
{legend.map(b => (
|
|
662
741
|
<Text>
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
|
|
1
|
+
988e2d2c901e9c5271b40684d0e30351ea8bae6c46ebb98c5067f6fb2944f1ab .codex-plugin/plugin.json
|
|
2
2
|
a7bebae9fb01c838d6c75fc784129e28be571233fa66ff5ee8039b574f35ccf7 assets/icon.png
|
|
3
3
|
fc38b64e4df1e0b72c35764bef6c4c05430d97c9009ad0ecc8a9e75b4deab232 configure-local.mjs
|
|
4
4
|
73dc6028b3b070257c7c31f252d11e55a9e096f4aed0f187f10cb21716e1e6d7 dist/client.js
|