claude-memory-admin 1.5.1 → 1.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +79 -124
- package/package.json +1 -1
- package/public/api.mjs +26 -0
- package/public/app.mjs +35 -2270
- package/public/bus.mjs +7 -0
- package/public/dialog.mjs +7 -0
- package/public/dialogs/add-entry.mjs +69 -0
- package/public/dialogs/bulk-delete.mjs +59 -0
- package/public/dialogs/delete.mjs +114 -0
- package/public/dialogs/hook-editor.mjs +54 -0
- package/public/dialogs/merge.mjs +81 -0
- package/public/dialogs/move.mjs +43 -0
- package/public/dialogs/remember-path.mjs +65 -0
- package/public/dialogs/store-delete.mjs +64 -0
- package/public/dialogs/trash.mjs +53 -0
- package/public/index.html +1 -0
- package/public/parts.mjs +65 -0
- package/public/state.mjs +48 -0
- package/public/store.mjs +121 -0
- package/public/styles.css +1 -1
- package/public/ui.mjs +44 -16
- package/public/views/cleanup.mjs +63 -0
- package/public/views/context.mjs +121 -0
- package/public/views/cost-issue.mjs +70 -0
- package/public/views/environment.mjs +12 -0
- package/public/views/header.mjs +183 -0
- package/public/views/index.mjs +122 -0
- package/public/views/issue.mjs +262 -0
- package/public/views/memories.mjs +156 -0
- package/public/views/memory-list.mjs +121 -0
- package/public/views/memory.mjs +32 -0
- package/public/views/path-check.mjs +103 -0
- package/public/views/search.mjs +100 -0
- package/public/views/segments.mjs +37 -0
- package/public/views/sessions.mjs +235 -0
- package/public/views/settings.mjs +121 -0
- package/public/views/stores.mjs +65 -0
- package/public/views/worklist.mjs +33 -0
- package/src/sessions.mjs +69 -1
package/README.md
CHANGED
|
@@ -21,13 +21,24 @@ itself, and worth saying out loud.
|
|
|
21
21
|
|
|
22
22
|
---
|
|
23
23
|
|
|
24
|
-
###
|
|
24
|
+
### Cleanup: what MEMORY.md costs you and what is broken, in one list
|
|
25
25
|
|
|
26
|
-

|
|
27
|
+
|
|
28
|
+
### Memory: the list, the index and the graph
|
|
29
|
+
|
|
30
|
+

|
|
27
31
|
|
|
28
32
|
### Graph: see how memories link to each other
|
|
29
33
|
|
|
30
|
-

|
|
34
|
+

|
|
35
|
+
|
|
36
|
+
### Environment: what every session loads before a project is even chosen
|
|
37
|
+
|
|
38
|
+

|
|
39
|
+
|
|
40
|
+
There is a light theme and a dark one, toggled with `t`; the Memory shot above is
|
|
41
|
+
the light one and the rest are dark.
|
|
31
42
|
|
|
32
43
|
---
|
|
33
44
|
|
|
@@ -66,6 +77,17 @@ project is to the cliff, and makes it quick to get back under it.
|
|
|
66
77
|
|
|
67
78
|
## What it does
|
|
68
79
|
|
|
80
|
+
Everything lives under three tabs, each answering one question:
|
|
81
|
+
|
|
82
|
+
| Tab | The question | Holds |
|
|
83
|
+
| --- | --- | --- |
|
|
84
|
+
| **Memory** | what is in here? | the memory list, `MEMORY.md`, the graph |
|
|
85
|
+
| **Cleanup** | what should I fix? | the load meter and one worst-first list of fixable things |
|
|
86
|
+
| **Environment** | what else does Claude load? | instructions, settings, sessions - all read-only |
|
|
87
|
+
|
|
88
|
+
Undo is a button in the project header rather than a fourth tab, because it is a
|
|
89
|
+
safety net and not a place you browse.
|
|
90
|
+
|
|
69
91
|
- **Projects by real path.** The directories on disk are slugified cwds
|
|
70
92
|
(`-Users-me-repos-Blog`) and the slugification is lossy. The true path is
|
|
71
93
|
recovered from the `cwd` field in the session transcripts stored next to each
|
|
@@ -93,23 +115,27 @@ project is to the cliff, and makes it quick to get back under it.
|
|
|
93
115
|
with snippets and match highlighting. Press `/` to jump to it.
|
|
94
116
|
- **Read each memory** with its frontmatter as structured metadata and
|
|
95
117
|
`[[wikilinks]]` as clickable links. Dead links are struck through in red.
|
|
96
|
-
- **
|
|
97
|
-
- **Graph** the wikilinks between memories. Hovering dims everything
|
|
98
|
-
a neighbour, which is the only practical way to read a dense cluster.
|
|
118
|
+
- **Cleanup** (see below).
|
|
119
|
+
- **Graph** the wikilinks between memories, under Memory. Hovering dims everything
|
|
120
|
+
that is not a neighbour, which is the only practical way to read a dense cluster.
|
|
99
121
|
- **Where each memory came from.** Claude Code stamps `originSessionId` into a
|
|
100
122
|
memory's frontmatter, and the transcript it names sits next to the store until
|
|
101
123
|
the sweep takes it. The memory reads *written in "Release process notes", on
|
|
102
124
|
`main`* while that transcript is there, and says so plainly once it is gone:
|
|
103
125
|
a swept id is struck through in red, like a dead wikilink, because why the
|
|
104
126
|
memory exists can no longer be traced from anything on disk.
|
|
105
|
-
- **Sessions
|
|
127
|
+
- **Sessions** (under Environment): the transcripts beside a store, with the retention window drawn
|
|
106
128
|
the way MEMORY.md's cutoff is - each session a tick, the sweep line where it
|
|
107
|
-
falls.
|
|
129
|
+
falls. Under it, a tile per day shades how much work that day held, and
|
|
130
|
+
clicking one narrows the list to it; the grid spans the retention window
|
|
131
|
+
rather than a fixed year, because a year of empty tiles would read as *no
|
|
132
|
+
sessions* when it only means *already swept*. Titles are the ones Claude Code
|
|
133
|
+
generated, falling back to the session
|
|
108
134
|
slug and then the opening prompt; a session that names itself nowhere in the
|
|
109
135
|
part read is shown by id rather than given an invented name. Only the head of
|
|
110
136
|
each file is ever read, so a 200MB store of transcripts costs a quarter of a
|
|
111
|
-
second, and nothing
|
|
112
|
-
- **
|
|
137
|
+
second, and nothing here deletes one.
|
|
138
|
+
- **What Cleanup checks**: orphans, dangling pointers, broken wikilinks, files linked only
|
|
113
139
|
mid-sentence, `name` fields that disagree with the filename, two files claiming
|
|
114
140
|
one `name`, a file bulleted twice, a blank `description`, a `type` outside the
|
|
115
141
|
four documented ones, a memory that is frontmatter and little else, a hook that
|
|
@@ -118,19 +144,20 @@ project is to the cliff, and makes it quick to get back under it.
|
|
|
118
144
|
been swept, a project whose last proof of its own path is about to be, and
|
|
119
145
|
sessions that produced no memory at all while auto memory was on. An orphan
|
|
120
146
|
can be given the `MEMORY.md` bullet it is missing without leaving the page.
|
|
121
|
-
- **Every count is visible before you click.** Each
|
|
122
|
-
amber for something to tidy and red for something actively
|
|
123
|
-
sidebar gives each store a dot of its worst severity - memory,
|
|
124
|
-
settings together. Which project is in trouble is the first
|
|
125
|
-
you, not something you find by opening
|
|
147
|
+
- **Every count is visible before you click.** Each of the three tabs carries a
|
|
148
|
+
badge, coloured amber for something to tidy and red for something actively
|
|
149
|
+
broken, and the sidebar gives each store a dot of its worst severity - memory,
|
|
150
|
+
instructions and settings together. Which project is in trouble is the first
|
|
151
|
+
thing the app tells you, not something you find by opening every tab in turn.
|
|
126
152
|
- **Dates you can trust.** Claude Code stamps `modified` into frontmatter, but
|
|
127
153
|
only on files that already have some, and never adds frontmatter to a file
|
|
128
154
|
without it. Anything falling back to the file's mtime is labelled, because
|
|
129
155
|
mtime is reset by any copy or restore.
|
|
130
|
-
- **
|
|
131
|
-
*instructions*, which is the other half of the startup budget
|
|
132
|
-
|
|
133
|
-
|
|
156
|
+
- **Instructions** (under Environment): what a session starting in this project
|
|
157
|
+
would load as *instructions*, which is the other half of the startup budget
|
|
158
|
+
(see below).
|
|
159
|
+
- **Settings** (under Environment): every layer Claude Code would read, side by
|
|
160
|
+
side, with the value that wins and the ones it shadows (see below).
|
|
134
161
|
- **Delete with cascade**, always reversible.
|
|
135
162
|
|
|
136
163
|
Everything above works the same on both kinds of store. The load meter, graph,
|
|
@@ -140,17 +167,20 @@ memory directory is a `MEMORY.md` index plus topic files under the same 200-line
|
|
|
140
167
|
|
|
141
168
|
## Keeping MEMORY.md small
|
|
142
169
|
|
|
143
|
-
The **
|
|
144
|
-
session and, past the limit, silently stops loading.
|
|
170
|
+
The **Cleanup** tab exists because a bloated index costs tokens on every single
|
|
171
|
+
session and, past the limit, silently stops loading. It is one list: the load
|
|
172
|
+
meter, then everything worth doing something about, worst first, each row
|
|
173
|
+
carrying its own fix. A finding and its fix are never on different tabs.
|
|
145
174
|
|
|
146
175
|
- **Load meter**. How much of the 200-line / 25KB budget the index uses, and
|
|
147
176
|
which of the two is binding. Frontmatter and HTML comments are excluded,
|
|
148
177
|
because Claude Code strips those before loading.
|
|
149
|
-
- **The cutoff, drawn where it falls**. Past the limit, the MEMORY.md
|
|
150
|
-
line across the file and dims everything below it, and
|
|
151
|
-
that stopped being loaded
|
|
178
|
+
- **The cutoff, drawn where it falls**. Past the limit, the MEMORY.md segment rules
|
|
179
|
+
a line across the file and dims everything below it, and Cleanup lists the
|
|
180
|
+
memories that stopped being loaded at the top of the worklist, each with
|
|
181
|
+
**Move up**. Because the stripping shifts every line, the cutoff
|
|
152
182
|
is mapped back to real line numbers rather than counted in the loaded text. The
|
|
153
|
-
|
|
183
|
+
segment reads the index either **rendered** — headings, lists and clickable entries,
|
|
154
184
|
with pointers to files that do not exist struck through — or as **source**, with
|
|
155
185
|
line numbers. Either way the cutoff is drawn in the same place. The choice is
|
|
156
186
|
remembered.
|
|
@@ -163,16 +193,18 @@ session and, past the limit, silently stops loading.
|
|
|
163
193
|
- **Move above the cutoff**. Past the limit, an entry is on disk but invisible.
|
|
164
194
|
**Move up** relocates its bullet, and the indented lines under it, to the end
|
|
165
195
|
of any section that starts above the cutoff. It moves one entry rather than
|
|
166
|
-
making room, so whatever is now last drops below the line instead - the
|
|
196
|
+
making room, so whatever is now last drops below the line instead - the app
|
|
167
197
|
says so before you do it.
|
|
168
198
|
- **Possible overlap**. Pairs of memories ranked by shared *rare* vocabulary, to
|
|
169
199
|
surface the same lesson saved three times from three sessions. It is a hint,
|
|
170
|
-
not a verdict. **Merge** folds one into the other: the body
|
|
171
|
-
heading you name, every `[[wikilink]]` that pointed at the source
|
|
172
|
-
rather than broken, a link the survivor had to the source becomes
|
|
173
|
-
instead of a self-link, and the two index bullets collapse to one.
|
|
174
|
-
|
|
175
|
-
|
|
200
|
+
not a verdict, so it sorts last. **Merge** folds one into the other: the body
|
|
201
|
+
moves under a heading you name, every `[[wikilink]]` that pointed at the source
|
|
202
|
+
is repointed rather than broken, a link the survivor had to the source becomes
|
|
203
|
+
plain text instead of a self-link, and the two index bullets collapse to one.
|
|
204
|
+
|
|
205
|
+
Bulk pruning is not here, because it is browsing rather than fixing: the **Memory**
|
|
206
|
+
list sorts by age, size or inbound links, and **Select** turns on the checkboxes so
|
|
207
|
+
several can go as one restore point. One list of memories, not two.
|
|
176
208
|
|
|
177
209
|
Anthropic's own guidance for the index: one line per entry, detail in the topic
|
|
178
210
|
files, merge or drop stale entries.
|
|
@@ -180,13 +212,13 @@ files, merge or drop stale entries.
|
|
|
180
212
|
## The other half of the budget
|
|
181
213
|
|
|
182
214
|
`MEMORY.md` is not the only thing loaded at the start of every session. The
|
|
183
|
-
**
|
|
215
|
+
**Environment** tab's **Instructions** segment resolves what else is, in load order: managed policy, your
|
|
184
216
|
`~/.claude/CLAUDE.md` and `~/.claude/rules/`, every `CLAUDE.md` and
|
|
185
217
|
`CLAUDE.local.md` from the filesystem root down to the project, `.claude/CLAUDE.md`,
|
|
186
218
|
and `.claude/rules/`, with `@path` imports expanded and `claudeMdExcludes` applied.
|
|
187
219
|
|
|
188
220
|
Unlike `MEMORY.md`, none of it is truncated: `CLAUDE.md` files load in full
|
|
189
|
-
however long they are. So
|
|
221
|
+
however long they are. So it reports cost rather than a cliff, and separates
|
|
190
222
|
what every session pays for from the path-scoped rules that only load on a match.
|
|
191
223
|
|
|
192
224
|
It also finds the failures that leave a file silently doing nothing:
|
|
@@ -212,7 +244,7 @@ Backticks are respected, so a `` `@README` `` in your prose is not reported as a
|
|
|
212
244
|
import, and neither is an email address.
|
|
213
245
|
|
|
214
246
|
Every file listed opens in place, so you can read what actually loads without
|
|
215
|
-
leaving the
|
|
247
|
+
leaving the page. Nothing here is editable: the app never rewrites a `CLAUDE.md`.
|
|
216
248
|
|
|
217
249
|
### The user scope on its own
|
|
218
250
|
|
|
@@ -226,7 +258,7 @@ It holds instructions rather than memory, so it is read-only and has no MEMORY.m
|
|
|
226
258
|
graph or trash. Search does not reach into it.
|
|
227
259
|
|
|
228
260
|
This is re-derived from the documented resolution rules rather than reported by
|
|
229
|
-
Claude Code, and the
|
|
261
|
+
Claude Code, and the app says so. Run `/context` in a session for the ground
|
|
230
262
|
truth, or the `InstructionsLoaded` hook to log exactly what loaded and why.
|
|
231
263
|
|
|
232
264
|
## The one check that reads your code
|
|
@@ -237,7 +269,7 @@ it takes the paths a memory names in a code span and asks whether anything in th
|
|
|
237
269
|
repository still matches them. A memory whose `Foo.cs` was renamed two months ago
|
|
238
270
|
still reads as authoritative, and nothing else on this page can tell.
|
|
239
271
|
|
|
240
|
-
Turn it on from the
|
|
272
|
+
Turn it on from the bottom of the Cleanup tab, per store; the choice is remembered in the browser
|
|
241
273
|
and nothing is written to disk. It walks the project once, skipping `.git`,
|
|
242
274
|
`node_modules`, build output and the like, and matches by suffix - a memory that
|
|
243
275
|
says `Infrastructure/Reporting/Foo.cs` for a file that really lives under
|
|
@@ -246,13 +278,14 @@ alone reported seven real paths in ten as missing. Only a token with a real sour
|
|
|
246
278
|
extension is treated as a claim about a file, because memories are also full of
|
|
247
279
|
HTTP routes and type names that no file was ever going to match.
|
|
248
280
|
|
|
249
|
-
It is a pointer, not a verdict, and the
|
|
281
|
+
It is a pointer, not a verdict, and the app says so: a file that moved reads the
|
|
250
282
|
same as one the memory never got right.
|
|
251
283
|
|
|
252
284
|
## Which settings are actually in force
|
|
253
285
|
|
|
254
286
|
Five files can set the keys this tool cares about, and the one everybody edits,
|
|
255
|
-
`~/.claude/settings.json`, is the weakest of them. The **
|
|
287
|
+
`~/.claude/settings.json`, is the weakest of them. The **Environment** tab's
|
|
288
|
+
**Settings** segment reads all
|
|
256
289
|
five and shows, per key, the value that wins and the ones it shadows, struck
|
|
257
290
|
through, each labelled with the file it came from:
|
|
258
291
|
|
|
@@ -267,9 +300,10 @@ It also names the failures that are otherwise silent:
|
|
|
267
300
|
- A file that parses but is not an object, or cannot be read at all.
|
|
268
301
|
- An `autoMemoryDirectory` that is neither absolute nor `~/`-prefixed.
|
|
269
302
|
- A value Claude Code accepts the key of but not the number, like a
|
|
270
|
-
`cleanupPeriodDays` below 1:
|
|
303
|
+
`cleanupPeriodDays` below 1: it shows what is written *and* what applies.
|
|
271
304
|
|
|
272
|
-
|
|
305
|
+
Every segment of Environment is read-only. It reports what is configured; it never
|
|
306
|
+
writes a setting.
|
|
273
307
|
|
|
274
308
|
## Everything it writes is reversible
|
|
275
309
|
|
|
@@ -282,12 +316,14 @@ operation, and they are trashed and restored as a single step.
|
|
|
282
316
|
one restore point. Session transcripts (`*.jsonl`) and the project folder are
|
|
283
317
|
never touched, only the contents of `memory/`.
|
|
284
318
|
|
|
285
|
-
**Remove link** in the
|
|
319
|
+
**Remove link** in the Cleanup tab clears a `[[wikilink]]` whose target no longer
|
|
286
320
|
exists. The markup goes and the words stay, so `see [[gone]] for details` becomes
|
|
287
321
|
`see gone for details`.
|
|
288
322
|
|
|
289
323
|
Everything lands in `memory/.trash/` with a restore record and comes back from the
|
|
290
|
-
|
|
324
|
+
**Undo** control in the project header, one undo per operation however many files
|
|
325
|
+
it touched. Undo is not a place you browse, so it is a button with a count rather
|
|
326
|
+
than a tab of its own.
|
|
291
327
|
|
|
292
328
|
The app never writes a memory file of its own. It edits `MEMORY.md` a line at a
|
|
293
329
|
time - a hook rewritten, a bullet added or moved - and each of those keeps every
|
|
@@ -363,87 +399,6 @@ No bundler and no build step to run it: the backend is `node:http` plus `node:fs
|
|
|
363
399
|
and the frontend is plain ES modules the browser loads directly. Two runtime
|
|
364
400
|
dependencies, `marked` and `dompurify`, both only for rendering memory bodies safely.
|
|
365
401
|
|
|
366
|
-
Styling is the one thing that is compiled. `styles/app.css` is the Tailwind v4
|
|
367
|
-
source and `public/styles.css` is its committed output, so an installed copy is
|
|
368
|
-
ready to serve and never builds anything. After editing the source, run
|
|
369
|
-
`npm run build:css` and commit the result — CI rebuilds it and fails on drift.
|
|
370
|
-
|
|
371
|
-
| Path | Purpose |
|
|
372
|
-
| --- | --- |
|
|
373
|
-
| `bin/claude-memory-admin.mjs` | CLI entry point and argument parsing |
|
|
374
|
-
| `server.mjs` | HTTP server: static files + JSON API |
|
|
375
|
-
| `src/projects.mjs` | Project discovery, slug → real path resolution |
|
|
376
|
-
| `src/settings.mjs` | Layered reads of Claude Code's settings files, and the report behind the Settings tab |
|
|
377
|
-
| `src/pathcache.mjs` | The opt-in record of confirmed project paths |
|
|
378
|
-
| `src/sessions.mjs` | Session transcripts: bounded head reads, retention, provenance |
|
|
379
|
-
| `src/stores.mjs` | Store discovery: the user scope, auto memory and the three agent scopes |
|
|
380
|
-
| `src/instructions.mjs` | CLAUDE.md chain, `@` imports and rules resolution |
|
|
381
|
-
| `src/parse.mjs` | `MEMORY.md` and frontmatter parsers, wikilinks |
|
|
382
|
-
| `src/checks.mjs` | The consistency checks, as pure functions over parsed data |
|
|
383
|
-
| `src/pathcheck.mjs` | The opt-in check of paths a memory names, against the repo |
|
|
384
|
-
| `src/model.mjs` | Joins index, files, graph and health into one model |
|
|
385
|
-
| `src/stats.mjs` | Load-limit accounting and overlap detection |
|
|
386
|
-
| `src/search.mjs` | Full-text search across every store |
|
|
387
|
-
| `src/mutate.mjs` | Delete / edit / merge / restore, the only code that writes |
|
|
388
|
-
| `styles/app.css` | Tailwind source, compiled to `public/styles.css` |
|
|
389
|
-
| `public/` | Frontend |
|
|
390
|
-
|
|
391
|
-
Tests run against committed fixtures under `test/fixtures/`: a projects store
|
|
392
|
-
encoding the awkward shapes real memory directories contain, including one index
|
|
393
|
-
deliberately past the load limit, an `agents/` tree covering all three subagent
|
|
394
|
-
scopes, and an `instructions/` tree covering the import and glob edge cases. Your own
|
|
395
|
-
`~/.claude/projects` is additionally checked when it exists, always on a
|
|
396
|
-
throwaway copy.
|
|
397
|
-
|
|
398
|
-
### Releasing
|
|
399
|
-
|
|
400
|
-
Publishing runs in CI. You do not bump anything by hand.
|
|
401
|
-
|
|
402
|
-
Go to **Actions, Release, Run workflow**, pick `patch`, `minor` or `major`, and
|
|
403
|
-
run it. The workflow tests, bumps `package.json` and `package-lock.json`, commits
|
|
404
|
-
and tags the bump, pushes both, then publishes to npm.
|
|
405
|
-
|
|
406
|
-
If you would rather cut the version locally, that still works:
|
|
407
|
-
|
|
408
|
-
```bash
|
|
409
|
-
npm version patch
|
|
410
|
-
git push --follow-tags
|
|
411
|
-
```
|
|
412
|
-
|
|
413
|
-
Pushing a `v*` tag publishes whatever is in `package.json`, after checking the
|
|
414
|
-
two agree. Either route refuses to publish a version that is already on npm,
|
|
415
|
-
because npm never allows one to be replaced.
|
|
416
|
-
|
|
417
|
-
It needs one repository secret:
|
|
418
|
-
|
|
419
|
-
| Secret | Value |
|
|
420
|
-
| --- | --- |
|
|
421
|
-
| `NPM_TOKEN` | an npm **Automation** access token with publish rights |
|
|
422
|
-
|
|
423
|
-
Add it under *Settings, Secrets and variables, Actions, New repository secret*.
|
|
424
|
-
An Automation token is the right kind because it bypasses 2FA, which an
|
|
425
|
-
unattended workflow cannot satisfy.
|
|
426
|
-
|
|
427
|
-
Releases are published with [npm provenance](https://docs.npmjs.com/generating-provenance-statements),
|
|
428
|
-
so every version carries a signed, verifiable record of the workflow run and
|
|
429
|
-
commit that built it. That needs the `id-token: write` permission the workflow
|
|
430
|
-
already requests, a public repository, and the `repository` field in
|
|
431
|
-
`package.json` pointing at this repo.
|
|
432
|
-
|
|
433
|
-
Two things that will bite if they apply to you: the workflow pushes the bump
|
|
434
|
-
commit to the default branch, so a branch protection rule that blocks pushes
|
|
435
|
-
will stop it; and the tag it pushes uses `GITHUB_TOKEN`, which by design does
|
|
436
|
-
not trigger other workflows, so there is no double publish.
|
|
437
|
-
|
|
438
|
-
### Screenshots and demo data
|
|
439
|
-
|
|
440
|
-
The screenshots come from an invented store, never a real one:
|
|
441
|
-
|
|
442
|
-
```bash
|
|
443
|
-
node scripts/demo-store.mjs /tmp/demo-store
|
|
444
|
-
npm start -- --root /tmp/demo-store
|
|
445
|
-
```
|
|
446
|
-
|
|
447
402
|
## License
|
|
448
403
|
|
|
449
404
|
MIT
|
package/package.json
CHANGED
package/public/api.mjs
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import * as ui from '/ui.mjs';
|
|
2
|
+
import { el, node } from '/dom.mjs';
|
|
3
|
+
|
|
4
|
+
export async function api(path, options) {
|
|
5
|
+
const response = await fetch(path, {
|
|
6
|
+
headers: { 'content-type': 'application/json' },
|
|
7
|
+
...options,
|
|
8
|
+
});
|
|
9
|
+
const data = await response.json().catch(() => ({ error: 'Bad response from server' }));
|
|
10
|
+
if (!response.ok) throw new Error(data.error || `Request failed (${response.status})`);
|
|
11
|
+
return data;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function toast(message, { error = false, action } = {}) {
|
|
15
|
+
const element = node('div', { class: ui.toast(error) });
|
|
16
|
+
element.append(document.createTextNode(message));
|
|
17
|
+
if (action) {
|
|
18
|
+
element.append(node('button', {
|
|
19
|
+
class: ui.toastAction,
|
|
20
|
+
text: action.label,
|
|
21
|
+
onclick: () => { element.remove(); action.run(); },
|
|
22
|
+
}));
|
|
23
|
+
}
|
|
24
|
+
el('toast-root').append(element);
|
|
25
|
+
setTimeout(() => element.remove(), action ? 12000 : 4500);
|
|
26
|
+
}
|