yarramate 1.25.0 → 1.25.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/dist/adapters/visual/layout-sidecar.d.ts +28 -0
- package/dist/adapters/visual/layout-sidecar.js +61 -0
- package/dist/adapters/visual/session-server.js +20 -39
- package/dist/schema-validation.d.ts +1 -0
- package/dist/schema-validation.js +1 -0
- package/dist/schema-validators.generated.d.ts +6 -0
- package/dist/schema-validators.generated.js +592 -1
- package/dist/visual-app-lib/editor.js +23853 -23213
- package/dist/visual-app-lib/types/adapters/visual/layout-sidecar.d.ts +28 -0
- package/dist/visual-app-lib/types/schema-validation.d.ts +1 -0
- package/dist/visual-app-lib/types/schema-validators.generated.d.ts +6 -0
- package/docs/CONSUMING-YARRAMATE.md +7 -0
- package/package.json +1 -1
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { VisualLayoutPositions, VisualLayoutRoutes } from './protocol-contract.js';
|
|
2
|
+
/** Where every host writes a projection's sidecar, and reads it back from. */
|
|
3
|
+
export declare const LAYOUT_SIDECAR_DIR = ".yarramate/visual-layout";
|
|
4
|
+
export declare const layoutSidecarPath: (projectionId: string) => string;
|
|
5
|
+
/** A path a store or directory holds that could be a sidecar. */
|
|
6
|
+
export declare const isLayoutSidecarPath: (path: string) => boolean;
|
|
7
|
+
export interface LayoutSidecars {
|
|
8
|
+
readonly layouts: Record<string, VisualLayoutPositions>;
|
|
9
|
+
readonly folds: Record<string, {
|
|
10
|
+
folded: string[];
|
|
11
|
+
unfolded: string[];
|
|
12
|
+
}>;
|
|
13
|
+
readonly routes: Record<string, VisualLayoutRoutes>;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* What the sidecars say, keyed by the projection id each one names.
|
|
17
|
+
*
|
|
18
|
+
* Presentation state must never fail a session (ADR 0023): a source that does
|
|
19
|
+
* not parse or does not validate is skipped, not reported. A sidecar written
|
|
20
|
+
* before #473 has neither fold list and says nothing about folding rather
|
|
21
|
+
* than "fold nothing" - the view's own default decides - so only a sidecar
|
|
22
|
+
* that STATES a fold yields a `folds` entry. A sidecar written before the
|
|
23
|
+
* routes says nothing about them, and the layout recomputes.
|
|
24
|
+
*/
|
|
25
|
+
export declare const readLayoutSidecars: (sources: Iterable<{
|
|
26
|
+
readonly path: string;
|
|
27
|
+
readonly source: string;
|
|
28
|
+
}>) => LayoutSidecars;
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The layout sidecar, read back the one way (#503).
|
|
3
|
+
*
|
|
4
|
+
* A drag saves `.yarramate/visual-layout/<projectionId>.yaml` (ADR 0085), and
|
|
5
|
+
* since 1.25.0 the routes and the fold state ride in it beside the positions
|
|
6
|
+
* (ADR 0147, #473). Two hosts write that file and two hosts have to read it:
|
|
7
|
+
* the session server from disk, the local host a product mounts from its
|
|
8
|
+
* store. Until this module the server had a reader and the local host had
|
|
9
|
+
* none, so on a mounted host a saved drag was written faithfully and never
|
|
10
|
+
* applied again - found by ApertureX on 1.25.0 (#503). One reader here, and
|
|
11
|
+
* the two cannot drift apart a second time.
|
|
12
|
+
*
|
|
13
|
+
* Pure: it takes sources and returns what the model carries. Whoever can read
|
|
14
|
+
* a directory or a store hands the bytes over.
|
|
15
|
+
*/
|
|
16
|
+
import { parse } from 'yaml';
|
|
17
|
+
import { validateVisualLayout } from '../../schema-validation.js';
|
|
18
|
+
/** Where every host writes a projection's sidecar, and reads it back from. */
|
|
19
|
+
export const LAYOUT_SIDECAR_DIR = '.yarramate/visual-layout';
|
|
20
|
+
export const layoutSidecarPath = (projectionId) => `${LAYOUT_SIDECAR_DIR}/${projectionId}.yaml`;
|
|
21
|
+
/** A path a store or directory holds that could be a sidecar. */
|
|
22
|
+
export const isLayoutSidecarPath = (path) => path.startsWith(`${LAYOUT_SIDECAR_DIR}/`) &&
|
|
23
|
+
!path.slice(LAYOUT_SIDECAR_DIR.length + 1).includes('/') &&
|
|
24
|
+
(path.endsWith('.yaml') || path.endsWith('.yml'));
|
|
25
|
+
/**
|
|
26
|
+
* What the sidecars say, keyed by the projection id each one names.
|
|
27
|
+
*
|
|
28
|
+
* Presentation state must never fail a session (ADR 0023): a source that does
|
|
29
|
+
* not parse or does not validate is skipped, not reported. A sidecar written
|
|
30
|
+
* before #473 has neither fold list and says nothing about folding rather
|
|
31
|
+
* than "fold nothing" - the view's own default decides - so only a sidecar
|
|
32
|
+
* that STATES a fold yields a `folds` entry. A sidecar written before the
|
|
33
|
+
* routes says nothing about them, and the layout recomputes.
|
|
34
|
+
*/
|
|
35
|
+
export const readLayoutSidecars = (sources) => {
|
|
36
|
+
const layouts = {};
|
|
37
|
+
const folds = {};
|
|
38
|
+
const routes = {};
|
|
39
|
+
for (const { source } of sources) {
|
|
40
|
+
let parsed;
|
|
41
|
+
try {
|
|
42
|
+
parsed = parse(source);
|
|
43
|
+
}
|
|
44
|
+
catch {
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
if (!validateVisualLayout(parsed))
|
|
48
|
+
continue;
|
|
49
|
+
const sidecar = parsed;
|
|
50
|
+
layouts[sidecar.projectionId] = sidecar.positions;
|
|
51
|
+
if (sidecar.routes !== undefined)
|
|
52
|
+
routes[sidecar.projectionId] = sidecar.routes;
|
|
53
|
+
if (sidecar.folded !== undefined || sidecar.unfolded !== undefined) {
|
|
54
|
+
folds[sidecar.projectionId] = {
|
|
55
|
+
folded: [...(sidecar.folded ?? [])],
|
|
56
|
+
unfolded: [...(sidecar.unfolded ?? [])],
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return { layouts, folds, routes };
|
|
61
|
+
};
|
|
@@ -6,7 +6,6 @@ import { basename, extname, join, relative, resolve, sep } from "node:path";
|
|
|
6
6
|
import { fileURLToPath } from "node:url";
|
|
7
7
|
import Ajv2020Module from "ajv/dist/2020.js";
|
|
8
8
|
import { WebSocketServer } from "ws";
|
|
9
|
-
import { parse } from "yaml";
|
|
10
9
|
import { VISUAL_LIMITS, VISUAL_PROTOCOL_VERSION, digestOf, parseVisualBrowserInput, parseVisualResponse, parseVisualSessionStarted, parseVisualStatus, toWireFileUri, visualBrowserInputType, } from "./protocol.js";
|
|
11
10
|
import { appendTerminalEvent, appendVisualEvent, appendVisualResponse, createVisualSession, isActionableVisualEvent, recoverVisualSession, removeVisualSession, writeVisualSessionDescriptor, } from "./session-store.js";
|
|
12
11
|
import { loadProjection, } from "../../projection.js";
|
|
@@ -17,17 +16,13 @@ import { planOperations, posixDirectoryOf, writeConflictDiagnostics, } from "../
|
|
|
17
16
|
import { createFileSystemStore } from "../../source-store.js";
|
|
18
17
|
import { emitYaml } from "../../yaml-emission.js";
|
|
19
18
|
import { loadWorkspaceManifest, } from "../../workspace.js";
|
|
20
|
-
import
|
|
21
|
-
import visualProjectionSchema from "../../../schema/yarramate-projection.schema.json" with { type: "json" };
|
|
19
|
+
import { LAYOUT_SIDECAR_DIR, isLayoutSidecarPath, layoutSidecarPath, readLayoutSidecars, } from "./layout-sidecar.js";
|
|
22
20
|
// The layout sidecar is adapter-owned presentation state (ADR 0023) that
|
|
23
21
|
// protocol.ts's validators never touch — the browser's own `layout.save`
|
|
24
22
|
// payload is already schema-validated by `parseVisualBrowserInput` before it
|
|
25
23
|
// reaches this module — so this file compiles its own single-purpose
|
|
26
24
|
// validator for the sidecar files it reads directly off disk.
|
|
27
25
|
const Ajv2020 = Ajv2020Module.default;
|
|
28
|
-
const layoutAjv = new Ajv2020({ allErrors: true });
|
|
29
|
-
layoutAjv.addSchema(visualProjectionSchema);
|
|
30
|
-
const validateVisualLayout = layoutAjv.compile(visualLayoutSchema);
|
|
31
26
|
/**
|
|
32
27
|
* Returned on every response. The policy admits no external origin at all, so
|
|
33
28
|
* neither a compromised model nor a hostile chat message can reach the network,
|
|
@@ -454,49 +449,30 @@ export const startVisualServer = async (options) => {
|
|
|
454
449
|
// Drag positions are adapter-owned presentation state (ADR 0023): never
|
|
455
450
|
// validated by Core. An invalid or unreadable sidecar is skipped exactly
|
|
456
451
|
// like a broken saved view above — presentation state must never fail a
|
|
457
|
-
// session.
|
|
458
|
-
|
|
459
|
-
const
|
|
460
|
-
|
|
461
|
-
const folds = {};
|
|
462
|
-
const routes = {};
|
|
452
|
+
// session. The reading itself is shared with the local host (#503): this
|
|
453
|
+
// host can read a directory, so it hands the bytes over.
|
|
454
|
+
const layoutDir = resolve(options.cwd, LAYOUT_SIDECAR_DIR);
|
|
455
|
+
const { layouts, folds, routes } = readLayoutSidecars((() => {
|
|
463
456
|
let entries;
|
|
464
457
|
try {
|
|
465
458
|
entries = readdirSync(layoutDir);
|
|
466
459
|
}
|
|
467
460
|
catch {
|
|
468
|
-
return
|
|
461
|
+
return [];
|
|
469
462
|
}
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
463
|
+
return entries.flatMap((entry) => {
|
|
464
|
+
const path = `${LAYOUT_SIDECAR_DIR}/${entry}`;
|
|
465
|
+
if (!isLayoutSidecarPath(path))
|
|
466
|
+
return [];
|
|
473
467
|
try {
|
|
474
|
-
|
|
475
|
-
const parsed = parse(source);
|
|
476
|
-
if (!validateVisualLayout(parsed))
|
|
477
|
-
continue;
|
|
478
|
-
const sidecar = parsed;
|
|
479
|
-
layouts[sidecar.projectionId] = sidecar.positions;
|
|
480
|
-
// The routes the canvas was drawing when it saved (ADR 0147). A
|
|
481
|
-
// sidecar written before them says nothing, and the layout recomputes.
|
|
482
|
-
if (sidecar.routes !== undefined)
|
|
483
|
-
routes[sidecar.projectionId] = sidecar.routes;
|
|
484
|
-
// A sidecar written before #473 has neither list, and says nothing
|
|
485
|
-
// about folding rather than saying "fold nothing" - the view's own
|
|
486
|
-
// default decides for it. Only a sidecar that STATES a fold overrides.
|
|
487
|
-
if (sidecar.folded !== undefined || sidecar.unfolded !== undefined) {
|
|
488
|
-
folds[sidecar.projectionId] = {
|
|
489
|
-
folded: [...(sidecar.folded ?? [])],
|
|
490
|
-
unfolded: [...(sidecar.unfolded ?? [])],
|
|
491
|
-
};
|
|
492
|
-
}
|
|
468
|
+
return [{ path, source: readFileSync(join(layoutDir, entry), "utf8") }];
|
|
493
469
|
}
|
|
494
470
|
catch {
|
|
495
471
|
// Skipped sidecar: presentation state must never fail a session.
|
|
472
|
+
return [];
|
|
496
473
|
}
|
|
497
|
-
}
|
|
498
|
-
|
|
499
|
-
})();
|
|
474
|
+
});
|
|
475
|
+
})());
|
|
500
476
|
// `request.initialModel.graph` is the caller's compile (`buildVisualModelGraph`,
|
|
501
477
|
// before invoking `yarramate-visual start`) and is only the fallback below:
|
|
502
478
|
// `recompileWorkspace` immediately below is the one path that actually fills
|
|
@@ -649,6 +625,11 @@ export const startVisualServer = async (options) => {
|
|
|
649
625
|
initialView: rendered.initialView,
|
|
650
626
|
documents: resolvedWorkspace.documents,
|
|
651
627
|
layouts: rendered.layouts,
|
|
628
|
+
// The fold state and the routes the sidecars said, or a save set,
|
|
629
|
+
// carried across the recompile like the positions are (#503): a
|
|
630
|
+
// commit is not a reason to forget how the reviewer arranged things.
|
|
631
|
+
...(rendered.folds === undefined ? {} : { folds: rendered.folds }),
|
|
632
|
+
...(rendered.routes === undefined ? {} : { routes: rendered.routes }),
|
|
652
633
|
// Minted from the bytes this compile just read, so what the browser
|
|
653
634
|
// renders and what it can later claim it rendered are the same read.
|
|
654
635
|
sourceDigests: Object.fromEntries(sources.map(({ path, source }) => [path, digestOf(source)])),
|
|
@@ -1441,7 +1422,7 @@ export const startVisualServer = async (options) => {
|
|
|
1441
1422
|
});
|
|
1442
1423
|
return;
|
|
1443
1424
|
}
|
|
1444
|
-
const path =
|
|
1425
|
+
const path = layoutSidecarPath(projectionId);
|
|
1445
1426
|
mkdirSync(layoutDir, { recursive: true });
|
|
1446
1427
|
writeFileSync(resolve(options.cwd, path), emitYaml({
|
|
1447
1428
|
format: "yarramate/visual-layout/v1",
|
|
@@ -39,4 +39,5 @@ export declare const validateEvidence: ValidateFunction;
|
|
|
39
39
|
export declare const validateCoreContract: ValidateFunction;
|
|
40
40
|
export declare const validateAdapterMapping: ValidateFunction;
|
|
41
41
|
export declare const validateCatalogue: ValidateFunction;
|
|
42
|
+
export declare const validateVisualLayout: ValidateFunction;
|
|
42
43
|
export declare const validateOperations: ValidateFunction;
|
|
@@ -40,4 +40,5 @@ export const validateEvidence = generated.validateEvidence;
|
|
|
40
40
|
export const validateCoreContract = generated.validateCoreContract;
|
|
41
41
|
export const validateAdapterMapping = generated.validateAdapterMapping;
|
|
42
42
|
export const validateCatalogue = generated.validateCatalogue;
|
|
43
|
+
export const validateVisualLayout = generated.validateVisualLayout;
|
|
43
44
|
export const validateOperations = generatedOperations.validateOperations;
|
|
@@ -52,4 +52,10 @@ declare function validate74(data: any, { instancePath, parentData, parentDataPro
|
|
|
52
52
|
instancePath?: string | undefined;
|
|
53
53
|
rootData?: any;
|
|
54
54
|
}): boolean;
|
|
55
|
+
export declare const validateVisualLayout: typeof validate82;
|
|
56
|
+
declare function validate82(data: any, { instancePath, parentData, parentDataProperty, rootData, dynamicAnchors }?: {
|
|
57
|
+
dynamicAnchors?: {} | undefined;
|
|
58
|
+
instancePath?: string | undefined;
|
|
59
|
+
rootData?: any;
|
|
60
|
+
}): boolean;
|
|
55
61
|
export {};
|