@xtrable-ltd/nanoesis 0.1.4 → 0.1.6
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/dist/adapter-azure-blob.js +1 -1
- package/dist/{chunk-S23S4ZAK.js → chunk-Y7PUOM5I.js} +86 -49
- package/dist/editor-api.d.ts +19 -7
- package/dist/editor-api.js +9 -43
- package/dist/index.d.ts +15 -0
- package/dist/index.js +1 -1
- package/editor/assets/{TemplatesPane-Cjwu8SOY.js → TemplatesPane-8SToGitk.js} +172 -172
- package/editor/assets/{cssMode-CIqtqlOX.js → cssMode-BgMFxIL5.js} +1 -1
- package/editor/assets/{freemarker2-03aLbgAQ.js → freemarker2-C1n0dZBT.js} +1 -1
- package/editor/assets/{handlebars-CsCT0Kz-.js → handlebars-DHtQKz6I.js} +1 -1
- package/editor/assets/{html-BMDaxhoV.js → html-DCKECHJy.js} +1 -1
- package/editor/assets/{htmlMode-BmX-GJzB.js → htmlMode-DIxPoVyG.js} +1 -1
- package/editor/assets/{index-DSooOnUR.css → index-BRPY7tEn.css} +1 -1
- package/editor/assets/{index-xCwd0bS3.js → index-BZ3Bij0z.js} +68 -73
- package/editor/assets/{javascript-y8MJYJ58.js → javascript-BQa7DljQ.js} +1 -1
- package/editor/assets/{jsonMode-DPpxOVap.js → jsonMode-CUqvw4kw.js} +1 -1
- package/editor/assets/{liquid-CGoDffSW.js → liquid-BdmTog1w.js} +1 -1
- package/editor/assets/{mdx-CMvr2LP2.js → mdx-CMmi05CR.js} +1 -1
- package/editor/assets/{python-Dg93CFNM.js → python-BxYWYFBZ.js} +1 -1
- package/editor/assets/{razor-D8HipAex.js → razor-NbjX7n9N.js} +1 -1
- package/editor/assets/{tsMode-Drl3yYU0.js → tsMode-DiCcyKwn.js} +1 -1
- package/editor/assets/{typescript-Dcqumk7N.js → typescript-CJcxRbel.js} +1 -1
- package/editor/assets/{xml-zoMMlERG.js → xml-CdXggTD3.js} +1 -1
- package/editor/assets/{yaml-BMaJnJHN.js → yaml-CUKQ9Cpf.js} +1 -1
- package/editor/index.html +2 -2
- package/package.json +1 -1
|
@@ -237,6 +237,20 @@ var IndexedStore = class {
|
|
|
237
237
|
store;
|
|
238
238
|
index;
|
|
239
239
|
loading;
|
|
240
|
+
/**
|
|
241
|
+
* Per-instance mutex (DESIGN §11d): every mutation (`write`/`delete`/`rename`/`reconcile`)
|
|
242
|
+
* runs through {@link serializeMutation}, which chains onto the previous mutation's
|
|
243
|
+
* completion. Without it, two concurrent mutations both read the cached `this.index`,
|
|
244
|
+
* each compute their "next" key set from that stale snapshot, and the second
|
|
245
|
+
* `saveIndex` clobbers the first — the file writes land in blob, but the index keeps
|
|
246
|
+
* stale references or loses fresh ones (the "I deleted it and it's still there" or
|
|
247
|
+
* "I added it and it didn't appear" bugs surfaced dogfooding the marketing site,
|
|
248
|
+
* 2026-05-28). Reads (`list`/`exists`/`readBytes`) are *not* serialised; an
|
|
249
|
+
* in-flight mutation simply means a reader sees the pre-mutation index, eventually
|
|
250
|
+
* consistent and safe. Crashes inside `work()` release the lock so a single failure
|
|
251
|
+
* does not deadlock subsequent operations.
|
|
252
|
+
*/
|
|
253
|
+
mutationLock = Promise.resolve();
|
|
240
254
|
/** The index, loaded once on first need and cached (mutations replace the cached copy). */
|
|
241
255
|
async loaded() {
|
|
242
256
|
if (this.index !== void 0) return this.index;
|
|
@@ -244,6 +258,21 @@ var IndexedStore = class {
|
|
|
244
258
|
this.index = await this.loading;
|
|
245
259
|
return this.index;
|
|
246
260
|
}
|
|
261
|
+
serializeMutation(work) {
|
|
262
|
+
const previous = this.mutationLock;
|
|
263
|
+
let release;
|
|
264
|
+
this.mutationLock = new Promise((resolve) => {
|
|
265
|
+
release = resolve;
|
|
266
|
+
});
|
|
267
|
+
return (async () => {
|
|
268
|
+
try {
|
|
269
|
+
await previous;
|
|
270
|
+
return await work();
|
|
271
|
+
} finally {
|
|
272
|
+
release();
|
|
273
|
+
}
|
|
274
|
+
})();
|
|
275
|
+
}
|
|
247
276
|
async list(dir) {
|
|
248
277
|
return childrenOf((await this.loaded()).keys, dir);
|
|
249
278
|
}
|
|
@@ -262,31 +291,35 @@ var IndexedStore = class {
|
|
|
262
291
|
* Create or overwrite `key`. The index is rewritten only when `key` is new (an
|
|
263
292
|
* overwrite leaves the key set unchanged, so editing an item is a single `put`).
|
|
264
293
|
*/
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
294
|
+
write(key, bytes) {
|
|
295
|
+
return this.serializeMutation(async () => {
|
|
296
|
+
const target = guarded(normalize(key));
|
|
297
|
+
await this.store.put(target, bytes);
|
|
298
|
+
const index = await this.loaded();
|
|
299
|
+
if (!index.keys.includes(target)) {
|
|
300
|
+
this.index = await saveIndex(this.store, index, [...index.keys, target]);
|
|
301
|
+
}
|
|
302
|
+
});
|
|
272
303
|
}
|
|
273
304
|
/**
|
|
274
305
|
* Delete a file, or a whole directory subtree (every key under `key/`). Idempotent: a
|
|
275
306
|
* path the index does not know is still deleted from the store (clearing an orphan),
|
|
276
307
|
* and deleting nothing is a no-op.
|
|
277
308
|
*/
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
309
|
+
delete(key) {
|
|
310
|
+
return this.serializeMutation(async () => {
|
|
311
|
+
const target = guarded(normalize(key));
|
|
312
|
+
const index = await this.loaded();
|
|
313
|
+
const prefix = `${target}/`;
|
|
314
|
+
const removed = index.keys.filter((k) => k === target || k.startsWith(prefix));
|
|
315
|
+
if (removed.length === 0) {
|
|
316
|
+
await this.store.delete(target);
|
|
317
|
+
return;
|
|
318
|
+
}
|
|
319
|
+
await Promise.all(removed.map((k) => this.store.delete(k)));
|
|
320
|
+
const remaining = index.keys.filter((k) => k !== target && !k.startsWith(prefix));
|
|
321
|
+
this.index = await saveIndex(this.store, index, remaining);
|
|
322
|
+
});
|
|
290
323
|
}
|
|
291
324
|
/**
|
|
292
325
|
* Move/rename a file, or a whole directory subtree (every key under `from/` is remapped
|
|
@@ -295,32 +328,34 @@ var IndexedStore = class {
|
|
|
295
328
|
* `/api/rename` enforces. (Mutating the reserved namespace is a programmer error and
|
|
296
329
|
* still throws.)
|
|
297
330
|
*/
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
const
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
331
|
+
rename(from, to) {
|
|
332
|
+
return this.serializeMutation(async () => {
|
|
333
|
+
const source = guarded(normalize(from));
|
|
334
|
+
const dest = guarded(normalize(to));
|
|
335
|
+
if (source === dest) return { ok: true };
|
|
336
|
+
const index = await this.loaded();
|
|
337
|
+
const sourcePrefix = `${source}/`;
|
|
338
|
+
const affected = index.keys.filter((k) => k === source || k.startsWith(sourcePrefix));
|
|
339
|
+
if (affected.length === 0) return { ok: false, reason: "missing" };
|
|
340
|
+
const destPrefix = `${dest}/`;
|
|
341
|
+
if (index.keys.some((k) => k === dest || k.startsWith(destPrefix))) {
|
|
342
|
+
return { ok: false, reason: "exists" };
|
|
343
|
+
}
|
|
344
|
+
const moves = affected.map((k) => ({
|
|
345
|
+
from: k,
|
|
346
|
+
to: k === source ? dest : dest + k.slice(source.length)
|
|
347
|
+
}));
|
|
348
|
+
for (const move of moves) {
|
|
349
|
+
const bytes = await this.store.get(move.from);
|
|
350
|
+
if (bytes === void 0) continue;
|
|
351
|
+
await this.store.put(move.to, bytes);
|
|
352
|
+
await this.store.delete(move.from);
|
|
353
|
+
}
|
|
354
|
+
const movedFrom = new Set(moves.map((move) => move.from));
|
|
355
|
+
const next = index.keys.filter((k) => !movedFrom.has(k)).concat(moves.map((m) => m.to));
|
|
356
|
+
this.index = await saveIndex(this.store, index, next);
|
|
357
|
+
return { ok: true };
|
|
358
|
+
});
|
|
324
359
|
}
|
|
325
360
|
/**
|
|
326
361
|
* Rebuild this store's index from the *actual* keys the underlying store holds (DESIGN
|
|
@@ -330,10 +365,12 @@ var IndexedStore = class {
|
|
|
330
365
|
* {@link reconcileIndex} on a fresh store, this also refreshes the cached in-memory
|
|
331
366
|
* index, so this live instance sees the recovered files immediately.
|
|
332
367
|
*/
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
368
|
+
reconcile(actualKeys) {
|
|
369
|
+
return this.serializeMutation(async () => {
|
|
370
|
+
const result = await reconcileIndex(this.store, actualKeys);
|
|
371
|
+
this.index = result.index;
|
|
372
|
+
return result;
|
|
373
|
+
});
|
|
337
374
|
}
|
|
338
375
|
};
|
|
339
376
|
function guarded(key) {
|
package/dist/editor-api.d.ts
CHANGED
|
@@ -216,16 +216,28 @@ declare function readMcpResource(uri: string): {
|
|
|
216
216
|
* §11d) and bundled here as **pure data**, so the engine's purity is untouched and the
|
|
217
217
|
* scaffold works identically for every host and the MCP server with no per-host wiring.
|
|
218
218
|
*
|
|
219
|
-
*
|
|
220
|
-
*
|
|
221
|
-
*
|
|
222
|
-
*
|
|
223
|
-
*
|
|
219
|
+
* Auto-fires on first sign-in to an empty store (revised 2026-05-28). The earlier behaviour
|
|
220
|
+
* — a confirm prompt that the user could dismiss — left a user who said no with no path
|
|
221
|
+
* forward (the empty Content workspace has no signposted route to the Dev workspace, and
|
|
222
|
+
* every alternative to scaffolding needs templates to already exist). Removing the choice
|
|
223
|
+
* removes a concept; the scaffold is idempotent and gate-clean, so doing it always is safe.
|
|
224
|
+
*
|
|
225
|
+
* Deliberately minimal (revised 2026-05-28, owner's call):
|
|
226
|
+
* - `templates/home.html` is a **full HTML document** (DESIGN §6.10: with no
|
|
227
|
+
* `templates/document.html`, a page template is the whole document) — no shared shell.
|
|
228
|
+
* The user can extract one later when they add a second page; for the bootstrap state,
|
|
229
|
+
* three files is simpler than four.
|
|
230
|
+
* - `public/styles.css` for baseline readability — a one-page site without any CSS reads
|
|
231
|
+
* poorly, and the styles.css path is the editor's canonical "your site's CSS" location.
|
|
232
|
+
* - `content/index.json` with `isPublished: true` (revised 2026-05-28). The earlier
|
|
233
|
+
* "draft, so going live is deliberate" framing turned out to be a footgun: a new user
|
|
234
|
+
* would hit Publish and get the "nothing went live" warning without obvious cause. A
|
|
235
|
+
* published placeholder is honest about what a publish will do; the user's first edit
|
|
236
|
+
* is to replace the placeholder copy, not to also remember to toggle a flag.
|
|
224
237
|
*/
|
|
225
238
|
/**
|
|
226
239
|
* The starter files, keyed by working-store path. Written through the store's `write`
|
|
227
|
-
* (so the index records each)
|
|
228
|
-
* that depends on them, though the store has no cross-file ordering requirement.
|
|
240
|
+
* (so the index records each). The store has no cross-file ordering requirement.
|
|
229
241
|
*/
|
|
230
242
|
declare const SCAFFOLD_FILES: Readonly<Record<string, string>>;
|
|
231
243
|
|
package/dist/editor-api.js
CHANGED
|
@@ -8,45 +8,32 @@ import {
|
|
|
8
8
|
loadTemplate,
|
|
9
9
|
renderReferenceMarkdown,
|
|
10
10
|
validateSite
|
|
11
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-Y7PUOM5I.js";
|
|
12
12
|
|
|
13
13
|
// ../editor-api/src/scaffold.ts
|
|
14
|
-
var
|
|
14
|
+
var HOME_HTML = `<!doctype html>
|
|
15
15
|
<html lang="en">
|
|
16
16
|
<head>
|
|
17
17
|
<meta charset="utf-8" />
|
|
18
18
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
19
19
|
<title>{title}</title>
|
|
20
|
-
<meta name="description" content="{meta_description}" />
|
|
21
20
|
<link rel="stylesheet" href="/styles.css" />
|
|
22
21
|
</head>
|
|
23
22
|
<body>
|
|
24
|
-
<header class="site-header">
|
|
25
|
-
<a class="brand" href="/">Your site</a>
|
|
26
|
-
</header>
|
|
27
23
|
<main>
|
|
28
|
-
<
|
|
24
|
+
<h1>{title}</h1>
|
|
25
|
+
<div class="prose" data-type="richtext">{body}</div>
|
|
29
26
|
</main>
|
|
30
|
-
<footer class="site-footer">
|
|
31
|
-
<p>Built with nanoesis \u2014 static HTML, no runtime.</p>
|
|
32
|
-
</footer>
|
|
33
27
|
</body>
|
|
34
28
|
</html>
|
|
35
29
|
`;
|
|
36
|
-
var HOME_HTML = `<section class="hero">
|
|
37
|
-
<h1>{title}</h1>
|
|
38
|
-
<p class="lead">{lead}</p>
|
|
39
|
-
</section>
|
|
40
|
-
<div class="prose" data-type="richtext">{body}</div>
|
|
41
|
-
`;
|
|
42
30
|
var HOME_JSON = `${JSON.stringify(
|
|
43
31
|
{
|
|
44
32
|
template: "home",
|
|
45
33
|
title: "Welcome",
|
|
46
|
-
isPublished:
|
|
34
|
+
isPublished: true,
|
|
47
35
|
fields: {
|
|
48
|
-
|
|
49
|
-
body: "<p>This is your new site. It compiles to plain static HTML \u2014 there is no server and no client framework, and nothing of the editor survives into production.</p>"
|
|
36
|
+
body: "<p>This is your starter home page. Edit it in the nanoesis editor and click Publish to update your site.</p>"
|
|
50
37
|
}
|
|
51
38
|
},
|
|
52
39
|
null,
|
|
@@ -67,30 +54,16 @@ body {
|
|
|
67
54
|
margin: 0;
|
|
68
55
|
}
|
|
69
56
|
|
|
70
|
-
|
|
71
|
-
main,
|
|
72
|
-
.site-footer {
|
|
57
|
+
main {
|
|
73
58
|
max-width: var(--max);
|
|
74
59
|
margin-inline: auto;
|
|
75
60
|
padding: 1.5rem 1.25rem;
|
|
76
61
|
}
|
|
77
62
|
|
|
78
|
-
|
|
79
|
-
font-weight: 700;
|
|
80
|
-
text-decoration: none;
|
|
81
|
-
color: var(--ink);
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
.hero h1 {
|
|
63
|
+
h1 {
|
|
85
64
|
font-size: 2.25rem;
|
|
86
65
|
letter-spacing: -0.01em;
|
|
87
|
-
margin: 0 0
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
.lead {
|
|
91
|
-
font-size: 1.2rem;
|
|
92
|
-
color: var(--muted);
|
|
93
|
-
margin: 0 0 2rem;
|
|
66
|
+
margin: 0 0 1rem;
|
|
94
67
|
}
|
|
95
68
|
|
|
96
69
|
.prose {
|
|
@@ -100,15 +73,8 @@ main,
|
|
|
100
73
|
a {
|
|
101
74
|
color: var(--accent);
|
|
102
75
|
}
|
|
103
|
-
|
|
104
|
-
.site-footer {
|
|
105
|
-
color: var(--muted);
|
|
106
|
-
font-size: 0.9rem;
|
|
107
|
-
border-top: 1px solid #eee;
|
|
108
|
-
}
|
|
109
76
|
`;
|
|
110
77
|
var SCAFFOLD_FILES = {
|
|
111
|
-
"templates/document.html": DOCUMENT_HTML,
|
|
112
78
|
"templates/home.html": HOME_HTML,
|
|
113
79
|
"public/styles.css": STYLES_CSS,
|
|
114
80
|
"content/index.json": HOME_JSON
|
package/dist/index.d.ts
CHANGED
|
@@ -281,9 +281,24 @@ declare class IndexedStore implements WorkingStore {
|
|
|
281
281
|
private readonly store;
|
|
282
282
|
private index;
|
|
283
283
|
private loading;
|
|
284
|
+
/**
|
|
285
|
+
* Per-instance mutex (DESIGN §11d): every mutation (`write`/`delete`/`rename`/`reconcile`)
|
|
286
|
+
* runs through {@link serializeMutation}, which chains onto the previous mutation's
|
|
287
|
+
* completion. Without it, two concurrent mutations both read the cached `this.index`,
|
|
288
|
+
* each compute their "next" key set from that stale snapshot, and the second
|
|
289
|
+
* `saveIndex` clobbers the first — the file writes land in blob, but the index keeps
|
|
290
|
+
* stale references or loses fresh ones (the "I deleted it and it's still there" or
|
|
291
|
+
* "I added it and it didn't appear" bugs surfaced dogfooding the marketing site,
|
|
292
|
+
* 2026-05-28). Reads (`list`/`exists`/`readBytes`) are *not* serialised; an
|
|
293
|
+
* in-flight mutation simply means a reader sees the pre-mutation index, eventually
|
|
294
|
+
* consistent and safe. Crashes inside `work()` release the lock so a single failure
|
|
295
|
+
* does not deadlock subsequent operations.
|
|
296
|
+
*/
|
|
297
|
+
private mutationLock;
|
|
284
298
|
constructor(store: BlobStore);
|
|
285
299
|
/** The index, loaded once on first need and cached (mutations replace the cached copy). */
|
|
286
300
|
private loaded;
|
|
301
|
+
private serializeMutation;
|
|
287
302
|
list(dir: string): Promise<readonly DirEntry[]>;
|
|
288
303
|
readText(path: string): Promise<string>;
|
|
289
304
|
readBytes(path: string): Promise<Uint8Array>;
|