@jini-ai/cms 0.5.0 → 0.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.5.1 — 2026-10-06
|
|
4
|
+
|
|
5
|
+
- Navigation: the `menus_update_menu_tree` schema declares the `entryType`/`lastKnownHref` hints on entry targets, and `updateMenuTree` carries a stored hint forward when the same item still targets the same entry and the submitted target omits it, so echoing a read no longer marks the menu changed.
|
|
6
|
+
|
|
3
7
|
## 0.5.0 — 2026-10-05
|
|
4
8
|
|
|
5
9
|
### BREAKING
|
|
@@ -84,7 +84,15 @@ const NAV_TARGET_SCHEMA = {
|
|
|
84
84
|
type: "object",
|
|
85
85
|
additionalProperties: false,
|
|
86
86
|
required: ["kind", "entryId"],
|
|
87
|
-
properties: {
|
|
87
|
+
properties: {
|
|
88
|
+
kind: { const: "entryRef" },
|
|
89
|
+
entryId: { type: "string", description: "Target entry id (e.g. a page or post)." },
|
|
90
|
+
// Declared so a read can be echoed back unchanged: these were stored on page links but absent
|
|
91
|
+
// here, so every agent save dropped them (dry run 2026-10-05). The save also keeps the stored
|
|
92
|
+
// value when omitted for the same item id + entryId (`carryForwardEntryHints`).
|
|
93
|
+
entryType: { type: "string", description: "Content-type hint, e.g. 'page' or 'post'. Copy it unchanged from the read; omit for a new link." },
|
|
94
|
+
lastKnownHref: { type: "string", description: "The entry's last known public URL, e.g. '/about'. Copy it unchanged from the read; omit for a new link." },
|
|
95
|
+
},
|
|
88
96
|
},
|
|
89
97
|
{
|
|
90
98
|
type: "object",
|
|
@@ -207,7 +215,7 @@ export const menusAgentToolCatalog = [
|
|
|
207
215
|
name: "menus_update_menu_tree",
|
|
208
216
|
description: "Replaces an existing menu's whole item tree (and optionally its title/slug) in one version-guarded write. " +
|
|
209
217
|
"Always a whole-tree replace — there is no per-item patch operation, so the submitted tree must include every " +
|
|
210
|
-
"item the menu keeps. To delete a menu, use trash_item with entityType 'menu' (it moves to the Trash and can be restored).",
|
|
218
|
+
"item the menu keeps, each with its fields copied unchanged from the read. To delete a menu, use trash_item with entityType 'menu' (it moves to the Trash and can be restored).",
|
|
211
219
|
sideEffects: "mutates-durable-state",
|
|
212
220
|
authorization: { permission: "admin.menus.update" },
|
|
213
221
|
inputSchema: {
|
|
@@ -198,6 +198,18 @@ export interface UpdateMenuTreeRequired {
|
|
|
198
198
|
export interface UpdateMenuTreeOptional {
|
|
199
199
|
limits?: TreeValidationLimits | undefined;
|
|
200
200
|
}
|
|
201
|
+
/**
|
|
202
|
+
* Whole-tree saves arrive from callers that may not know an item's stored hint fields (an agent
|
|
203
|
+
* echoing a read through a schema, an older editor build). Dropping them on every save made an
|
|
204
|
+
* untouched menu look changed (dry run 2026-10-05: ~30 header page links lost `entryType: "page"`
|
|
205
|
+
* on one assistant save and the menu showed pending-to-live). So a hint is kept when the same item
|
|
206
|
+
* id still points at the same entry and the submitted target leaves that hint out; a re-pointed
|
|
207
|
+
* link or an explicitly submitted hint is taken as sent.
|
|
208
|
+
*/
|
|
209
|
+
export declare function carryForwardEntryHints({ previous, next }: {
|
|
210
|
+
previous: readonly NavItemNode[];
|
|
211
|
+
next: readonly NavItemNode[];
|
|
212
|
+
}, _optional?: {}): NavItemNode[];
|
|
201
213
|
/**
|
|
202
214
|
* Replaces a menu's whole item tree, guarded by optimistic concurrency on the
|
|
203
215
|
* entry `version` (matches the host's `updatePost` pattern): `expectedVersion` must equal the version
|
|
@@ -327,6 +327,44 @@ export async function createMenu(required, optional = {}) {
|
|
|
327
327
|
}));
|
|
328
328
|
return { menu };
|
|
329
329
|
}
|
|
330
|
+
/** The stored `entryRef` hint fields a whole-tree save carries forward when the submitted target omits them. */
|
|
331
|
+
const ENTRY_HINT_KEYS = ["entryType", "lastKnownHref"];
|
|
332
|
+
function entryTargetsById(nodes, into = new Map()) {
|
|
333
|
+
for (const node of nodes) {
|
|
334
|
+
if (node.target?.kind === "entryRef")
|
|
335
|
+
into.set(node.id, node.target);
|
|
336
|
+
if (node.children)
|
|
337
|
+
entryTargetsById(node.children, into);
|
|
338
|
+
}
|
|
339
|
+
return into;
|
|
340
|
+
}
|
|
341
|
+
function withStoredHints(target, stored) {
|
|
342
|
+
if (target.kind !== "entryRef" || !stored || stored.entryId !== target.entryId)
|
|
343
|
+
return target;
|
|
344
|
+
const merged = { ...target };
|
|
345
|
+
for (const key of ENTRY_HINT_KEYS) {
|
|
346
|
+
if (merged[key] === undefined && stored[key] !== undefined)
|
|
347
|
+
merged[key] = stored[key];
|
|
348
|
+
}
|
|
349
|
+
return merged;
|
|
350
|
+
}
|
|
351
|
+
/**
|
|
352
|
+
* Whole-tree saves arrive from callers that may not know an item's stored hint fields (an agent
|
|
353
|
+
* echoing a read through a schema, an older editor build). Dropping them on every save made an
|
|
354
|
+
* untouched menu look changed (dry run 2026-10-05: ~30 header page links lost `entryType: "page"`
|
|
355
|
+
* on one assistant save and the menu showed pending-to-live). So a hint is kept when the same item
|
|
356
|
+
* id still points at the same entry and the submitted target leaves that hint out; a re-pointed
|
|
357
|
+
* link or an explicitly submitted hint is taken as sent.
|
|
358
|
+
*/
|
|
359
|
+
export function carryForwardEntryHints({ previous, next }, _optional = {}) {
|
|
360
|
+
const stored = entryTargetsById(previous);
|
|
361
|
+
const walk = (nodes) => nodes.map((node) => ({
|
|
362
|
+
...node,
|
|
363
|
+
target: withStoredHints(node.target, stored.get(node.id)),
|
|
364
|
+
children: node.children ? walk(node.children) : undefined,
|
|
365
|
+
}));
|
|
366
|
+
return walk(next);
|
|
367
|
+
}
|
|
330
368
|
/**
|
|
331
369
|
* Replaces a menu's whole item tree, guarded by optimistic concurrency on the
|
|
332
370
|
* entry `version` (matches the host's `updatePost` pattern): `expectedVersion` must equal the version
|
|
@@ -369,7 +407,10 @@ export async function updateMenuTree(required, optional = {}) {
|
|
|
369
407
|
throw new MenuConflictError({ message: `slug '${slug}' already exists` });
|
|
370
408
|
}
|
|
371
409
|
}
|
|
372
|
-
const items =
|
|
410
|
+
const items = carryForwardEntryHints({
|
|
411
|
+
previous: existing.doc.items,
|
|
412
|
+
next: validateAndCloneTree({ items: input.items }, optional.limits),
|
|
413
|
+
});
|
|
373
414
|
const menu = {
|
|
374
415
|
...existing,
|
|
375
416
|
title,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jini-ai/cms",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.1",
|
|
4
4
|
"description": "Content-model capability for Jini-hosted products: framework-free contracts, ports, and domain services in `/core` and per-domain subpaths; Node-bound adapters in domain-specific subpaths.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"repository": {
|