@campminder/menu-tree 0.0.1 → 0.1.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/dist/index.d.ts +9 -6
- package/dist/index.js +2 -9
- package/dist/model.d.ts +67 -0
- package/dist/model.js +12 -0
- package/package.json +6 -3
- package/src/menu-tree.schema.json +99 -3
package/dist/index.d.ts
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* @campminder/menu-tree — the ERP-agnostic menu data model for the `nav` Domain
|
|
3
|
+
* System: the structural types and the JSON Schema they project.
|
|
3
4
|
*
|
|
4
|
-
* The
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
5
|
+
* The schema is published as a subpath export rather than a JS value, so a
|
|
6
|
+
* consumer validates against the same file `nav` ships:
|
|
7
|
+
*
|
|
8
|
+
* import schema from "@campminder/menu-tree/menu-tree.schema.json";
|
|
8
9
|
*/
|
|
9
|
-
export
|
|
10
|
+
export type { MenuCategory, MenuLeaf, MenuSubCategory, MenuTree, VisibilityRule, } from "./model.js";
|
|
11
|
+
/** Kept as a runtime export so the cross-host consumption smoke test has something to assert. */
|
|
12
|
+
export declare const VERSION = "0.1.0";
|
package/dist/index.js
CHANGED
|
@@ -1,9 +1,2 @@
|
|
|
1
|
-
/**
|
|
2
|
-
|
|
3
|
-
*
|
|
4
|
-
* The real structural model (MenuTree, MenuCategory, MenuSubCategory,
|
|
5
|
-
* MenuLeaf, VisibilityRule) ships in Floor 4 of
|
|
6
|
-
* docs/plans/2026-09-02-feature-menu-model-and-drift-guard-plan.md. This
|
|
7
|
-
* floor proves the package builds, exports, and is tested — nothing more.
|
|
8
|
-
*/
|
|
9
|
-
export const VERSION = "0.0.1";
|
|
1
|
+
/** Kept as a runtime export so the cross-host consumption smoke test has something to assert. */
|
|
2
|
+
export const VERSION = "0.1.0";
|
package/dist/model.d.ts
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The structural menu-tree types — the TypeScript projection of
|
|
3
|
+
* menu-tree.schema.json, and the data model ADR D4 assigns to the `nav` Domain
|
|
4
|
+
* System. Ported from `portals`' packages/components/src/menu-tree.ts on
|
|
5
|
+
* 2026-09-08 (Floor 4).
|
|
6
|
+
*
|
|
7
|
+
* Nothing here is Campminder-specific and nothing here may become so. A tree
|
|
8
|
+
* *instance* (campminder.json), and the host context a filter evaluates against
|
|
9
|
+
* (FilterContext), both stay in the app shell per ADR D6. `nav` has no
|
|
10
|
+
* dependency on `portals`, ever.
|
|
11
|
+
*/
|
|
12
|
+
/**
|
|
13
|
+
* Visibility rules, evaluated by the host against its own context. Each rule is
|
|
14
|
+
* an independent predicate; all present rules must hold for the node to be
|
|
15
|
+
* visible.
|
|
16
|
+
*
|
|
17
|
+
* These are exactly the kinds the host evaluates. A `client` rule is NOT one of
|
|
18
|
+
* them — it was a phantom derived from a stale upstream schema and removed on
|
|
19
|
+
* 2026-09-01. Do not reintroduce it, and do not drop `featureKey`, which the
|
|
20
|
+
* same mistake omitted.
|
|
21
|
+
*/
|
|
22
|
+
export type VisibilityRule = {
|
|
23
|
+
kind: "feature";
|
|
24
|
+
featureIds: number[];
|
|
25
|
+
} | {
|
|
26
|
+
kind: "featureKey";
|
|
27
|
+
featureKeys: string[];
|
|
28
|
+
} | {
|
|
29
|
+
kind: "versionBranch";
|
|
30
|
+
mask: string;
|
|
31
|
+
};
|
|
32
|
+
/** A navigable endpoint. The only node type carrying a required `href`. */
|
|
33
|
+
export interface MenuLeaf {
|
|
34
|
+
id: string;
|
|
35
|
+
/** May carry an unresolved terminology token, e.g. `{{Bunk:Proper:Default}}`. */
|
|
36
|
+
label: string;
|
|
37
|
+
href: string;
|
|
38
|
+
sortOrder?: number;
|
|
39
|
+
newTab?: boolean;
|
|
40
|
+
popup?: boolean;
|
|
41
|
+
popupProperties?: string;
|
|
42
|
+
paperless?: boolean;
|
|
43
|
+
visibility?: VisibilityRule[];
|
|
44
|
+
}
|
|
45
|
+
export interface MenuSubCategory {
|
|
46
|
+
id: string;
|
|
47
|
+
label: string;
|
|
48
|
+
href?: string;
|
|
49
|
+
sortOrder?: number;
|
|
50
|
+
items: MenuLeaf[];
|
|
51
|
+
visibility?: VisibilityRule[];
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* `sortOrder` is required here and optional on the nodes below, matching the
|
|
55
|
+
* schema. See the schema's `$comment` for why that asymmetry is deliberate.
|
|
56
|
+
*/
|
|
57
|
+
export interface MenuCategory {
|
|
58
|
+
id: string;
|
|
59
|
+
label: string;
|
|
60
|
+
icon?: string;
|
|
61
|
+
href?: string;
|
|
62
|
+
sortOrder: number;
|
|
63
|
+
subCategories: MenuSubCategory[];
|
|
64
|
+
visibility?: VisibilityRule[];
|
|
65
|
+
}
|
|
66
|
+
/** A whole tree is a list of categories — there is no wrapper object. */
|
|
67
|
+
export type MenuTree = MenuCategory[];
|
package/dist/model.js
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The structural menu-tree types — the TypeScript projection of
|
|
3
|
+
* menu-tree.schema.json, and the data model ADR D4 assigns to the `nav` Domain
|
|
4
|
+
* System. Ported from `portals`' packages/components/src/menu-tree.ts on
|
|
5
|
+
* 2026-09-08 (Floor 4).
|
|
6
|
+
*
|
|
7
|
+
* Nothing here is Campminder-specific and nothing here may become so. A tree
|
|
8
|
+
* *instance* (campminder.json), and the host context a filter evaluates against
|
|
9
|
+
* (FilterContext), both stay in the app shell per ADR D6. `nav` has no
|
|
10
|
+
* dependency on `portals`, ever.
|
|
11
|
+
*/
|
|
12
|
+
export {};
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@campminder/menu-tree",
|
|
3
|
-
"version": "0.0
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "The ERP-agnostic menu data model for the nav Domain System: the structural menu-tree types and the JSON Schema they project.",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
7
7
|
"url": "git+https://dev.azure.com/CampMinderLLC/CampMinder/_git/nav",
|
|
@@ -23,11 +23,14 @@
|
|
|
23
23
|
"access": "public"
|
|
24
24
|
},
|
|
25
25
|
"devDependencies": {
|
|
26
|
+
"@types/node": "^24",
|
|
27
|
+
"ajv": "^8.17.1",
|
|
26
28
|
"typescript": "^5.9.3",
|
|
27
29
|
"vitest": "^2.1.8"
|
|
28
30
|
},
|
|
29
31
|
"scripts": {
|
|
30
32
|
"build": "tsc -p tsconfig.json",
|
|
31
|
-
"test": "vitest run"
|
|
33
|
+
"test": "vitest run",
|
|
34
|
+
"typecheck": "tsc -p tsconfig.typecheck.json"
|
|
32
35
|
}
|
|
33
36
|
}
|
|
@@ -1,6 +1,102 @@
|
|
|
1
1
|
{
|
|
2
|
-
"$schema": "
|
|
2
|
+
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
3
3
|
"$id": "https://campminder.com/schemas/menu-tree.schema.json",
|
|
4
|
-
"title": "MenuTree
|
|
5
|
-
"
|
|
4
|
+
"title": "MenuTree",
|
|
5
|
+
"description": "The ERP-agnostic shape of a portal navigation tree. A tree instance (one ERP's menu) is NOT described here and does not belong in this package — ADR D6 puts the instance in the app shell.",
|
|
6
|
+
"$comment": "AUTHORITY: the host's C# model — MenuItem in Core/Websites/CampMinder/navigation/NavigationAccessor.cs, and the gates NavigationBuilder actually evaluates. NOT core-js's apps/side-nav/src/data/navigation-data.schema.json, which is stale: it declares a dead `client_ids` and omits the live `feature_keys`. Copying it produced a phantom `client` rule and silently dropped a real gate; corrected 2026-09-01. ONE DELIBERATE DIVERGENCE FROM THE HOST CONTRACT: `sortOrder` is REQUIRED on a category here, where the host leaves it optional. 27 category sort orders were lost in an upstream derivation and nothing caught it, because nothing required them. All 13 categories in the CampMinder tree carry one, so the tightening costs nothing today; it is stated rather than silent so a future portal that legitimately omits one knows to revisit it. `sortOrder` stays optional on sub-categories and leaves, where the real data genuinely omits it (1 of 48 sub-categories and 214 of 249 leaves). Draft-07 rather than 2020-12 so consumers can validate with a default `new Ajv()`.",
|
|
7
|
+
"definitions": {
|
|
8
|
+
"visibilityRule": {
|
|
9
|
+
"$comment": "Exactly the kinds the host evaluates, and no others. Each rule is an independent predicate; all present rules must hold for the node to be visible.",
|
|
10
|
+
"type": "object",
|
|
11
|
+
"oneOf": [
|
|
12
|
+
{
|
|
13
|
+
"properties": {
|
|
14
|
+
"kind": { "const": "feature" },
|
|
15
|
+
"featureIds": { "type": "array", "items": { "type": "integer" } }
|
|
16
|
+
},
|
|
17
|
+
"required": ["kind", "featureIds"],
|
|
18
|
+
"additionalProperties": false
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
"properties": {
|
|
22
|
+
"kind": { "const": "featureKey" },
|
|
23
|
+
"featureKeys": { "type": "array", "items": { "type": "string" } }
|
|
24
|
+
},
|
|
25
|
+
"required": ["kind", "featureKeys"],
|
|
26
|
+
"additionalProperties": false
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
"properties": {
|
|
30
|
+
"kind": { "const": "versionBranch" },
|
|
31
|
+
"mask": { "type": "string" }
|
|
32
|
+
},
|
|
33
|
+
"required": ["kind", "mask"],
|
|
34
|
+
"additionalProperties": false
|
|
35
|
+
}
|
|
36
|
+
]
|
|
37
|
+
},
|
|
38
|
+
"menuLeaf": {
|
|
39
|
+
"type": "object",
|
|
40
|
+
"properties": {
|
|
41
|
+
"id": { "type": "string" },
|
|
42
|
+
"label": {
|
|
43
|
+
"type": "string",
|
|
44
|
+
"$comment": "Deliberately unconstrained. Labels may carry unresolved terminology tokens such as `{{Bunk:Proper:Default}}`, which the app shell substitutes per tenant. A pattern here would break that."
|
|
45
|
+
},
|
|
46
|
+
"href": { "type": "string" },
|
|
47
|
+
"sortOrder": { "type": "integer" },
|
|
48
|
+
"newTab": { "type": "boolean" },
|
|
49
|
+
"popup": { "type": "boolean" },
|
|
50
|
+
"popupProperties": { "type": "string" },
|
|
51
|
+
"paperless": { "type": "boolean" },
|
|
52
|
+
"visibility": {
|
|
53
|
+
"type": "array",
|
|
54
|
+
"items": { "$ref": "#/definitions/visibilityRule" }
|
|
55
|
+
}
|
|
56
|
+
},
|
|
57
|
+
"required": ["id", "label", "href"],
|
|
58
|
+
"additionalProperties": false
|
|
59
|
+
},
|
|
60
|
+
"menuSubCategory": {
|
|
61
|
+
"type": "object",
|
|
62
|
+
"properties": {
|
|
63
|
+
"id": { "type": "string" },
|
|
64
|
+
"label": { "type": "string" },
|
|
65
|
+
"href": { "type": "string" },
|
|
66
|
+
"sortOrder": { "type": "integer" },
|
|
67
|
+
"items": {
|
|
68
|
+
"type": "array",
|
|
69
|
+
"items": { "$ref": "#/definitions/menuLeaf" }
|
|
70
|
+
},
|
|
71
|
+
"visibility": {
|
|
72
|
+
"type": "array",
|
|
73
|
+
"items": { "$ref": "#/definitions/visibilityRule" }
|
|
74
|
+
}
|
|
75
|
+
},
|
|
76
|
+
"required": ["id", "label", "items"],
|
|
77
|
+
"additionalProperties": false
|
|
78
|
+
},
|
|
79
|
+
"menuCategory": {
|
|
80
|
+
"type": "object",
|
|
81
|
+
"properties": {
|
|
82
|
+
"id": { "type": "string" },
|
|
83
|
+
"label": { "type": "string" },
|
|
84
|
+
"icon": { "type": "string" },
|
|
85
|
+
"href": { "type": "string" },
|
|
86
|
+
"sortOrder": { "type": "integer" },
|
|
87
|
+
"subCategories": {
|
|
88
|
+
"type": "array",
|
|
89
|
+
"items": { "$ref": "#/definitions/menuSubCategory" }
|
|
90
|
+
},
|
|
91
|
+
"visibility": {
|
|
92
|
+
"type": "array",
|
|
93
|
+
"items": { "$ref": "#/definitions/visibilityRule" }
|
|
94
|
+
}
|
|
95
|
+
},
|
|
96
|
+
"required": ["id", "label", "sortOrder", "subCategories"],
|
|
97
|
+
"additionalProperties": false
|
|
98
|
+
}
|
|
99
|
+
},
|
|
100
|
+
"type": "array",
|
|
101
|
+
"items": { "$ref": "#/definitions/menuCategory" }
|
|
6
102
|
}
|