@jskit-ai/console-web 0.1.127 → 0.1.129

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/package.json CHANGED
@@ -1,14 +1,14 @@
1
1
  {
2
2
  "name": "@jskit-ai/console-web",
3
- "version": "0.1.127",
3
+ "version": "0.1.129",
4
4
  "type": "module",
5
5
  "scripts": {
6
6
  "test": "node --test"
7
7
  },
8
8
  "dependencies": {
9
- "@jskit-ai/auth-web": "0.1.159",
10
- "@jskit-ai/console-core": "0.1.122",
11
- "@jskit-ai/shell-web": "0.1.163"
9
+ "@jskit-ai/auth-web": "0.1.161",
10
+ "@jskit-ai/console-core": "0.1.124",
11
+ "@jskit-ai/shell-web": "0.1.165"
12
12
  },
13
13
  "description": "Authenticated console surface scaffold and surface policy wiring.",
14
14
  "jskit": {
@@ -36,11 +36,7 @@
36
36
  "subpath": "./client",
37
37
  "summary": "Exports no runtime API today (reserved console surface package entrypoint)."
38
38
  }
39
- ],
40
- "containerTokens": {
41
- "server": [],
42
- "client": []
43
- }
39
+ ]
44
40
  },
45
41
  "ui": {
46
42
  "placements": {
@@ -50,7 +46,7 @@
50
46
  "surfaces": [
51
47
  "console"
52
48
  ],
53
- "source": "templates/src/pages/console/settings.vue"
49
+ "source": "patterns/console-surface/example/src/pages/console/settings.vue"
54
50
  }
55
51
  ],
56
52
  "topology": {
@@ -95,7 +91,7 @@
95
91
  ],
96
92
  "order": 600,
97
93
  "when": "auth.authenticated === true && surfaceAccess.consoleowner === true && surface !== \"console\"",
98
- "source": "mutations.text#console-web-profile-menu-console-placement"
94
+ "source": "patterns/console-surface/example/src/pages/console/index.vue"
99
95
  },
100
96
  {
101
97
  "id": "console.web.menu.settings",
@@ -106,119 +102,11 @@
106
102
  ],
107
103
  "order": 100,
108
104
  "when": "auth.authenticated === true",
109
- "source": "mutations.text#console-web-console-settings-placement"
105
+ "source": "patterns/console-surface/example/src/pages/console/settings.vue"
110
106
  }
111
107
  ]
112
108
  }
113
109
  }
114
- },
115
- "mutations": {
116
- "dependencies": {
117
- "runtime": {
118
- "@jskit-ai/auth-web": "0.1.159",
119
- "@jskit-ai/console-core": "0.1.122",
120
- "@jskit-ai/shell-web": "0.1.163"
121
- },
122
- "dev": {}
123
- },
124
- "packageJson": {
125
- "scripts": {
126
- "server:console": "SERVER_SURFACE=console node ./bin/server.js",
127
- "dev:console": "VITE_SURFACE=console vite",
128
- "build:console": "VITE_SURFACE=console vite build"
129
- }
130
- },
131
- "procfile": {},
132
- "files": [
133
- {
134
- "from": "templates/src/pages/console.vue",
135
- "toSurface": "console",
136
- "toSurfaceRoot": true,
137
- "ownership": "app",
138
- "reason": "Install shell-driven console wrapper page.",
139
- "category": "console-web",
140
- "id": "console-web-page-console-wrapper"
141
- },
142
- {
143
- "from": "templates/src/pages/console/index.vue",
144
- "toSurface": "console",
145
- "toSurfacePath": "index.vue",
146
- "ownership": "app",
147
- "reason": "Install shell-driven console page starter.",
148
- "category": "console-web",
149
- "id": "console-web-page-console"
150
- },
151
- {
152
- "from": "templates/src/pages/console/settings.vue",
153
- "toSurface": "console",
154
- "toSurfacePath": "settings.vue",
155
- "ownership": "app",
156
- "reason": "Install console settings shell route scaffold for console-web.",
157
- "category": "console-web",
158
- "id": "console-web-page-console-settings-shell"
159
- },
160
- {
161
- "from": "templates/src/pages/console/settings/index.vue",
162
- "toSurface": "console",
163
- "toSurfacePath": "settings/index.vue",
164
- "ownership": "app",
165
- "reason": "Install console settings index stub scaffold for app-owned landing or redirect behavior.",
166
- "category": "console-web",
167
- "id": "console-web-page-console-settings"
168
- }
169
- ],
170
- "text": [
171
- {
172
- "op": "append-text",
173
- "file": "config/surfaceAccessPolicies.js",
174
- "position": "bottom",
175
- "skipIfContains": "surfaceAccessPolicies.console_owner = {",
176
- "value": "\nsurfaceAccessPolicies.console_owner = {\n requireAuth: true,\n requireFlagsAll: [\"console_owner\"]\n};\n",
177
- "reason": "Register console-owner surface access policy for the console surface.",
178
- "category": "console-web",
179
- "id": "console-web-surface-access-policies-console-owner"
180
- },
181
- {
182
- "op": "append-text",
183
- "file": "config/public.js",
184
- "position": "bottom",
185
- "skipIfContains": "config.surfaceDefinitions.console = {",
186
- "value": "\nconfig.surfaceDefinitions.console = {\n id: \"console\",\n label: \"Console\",\n pagesRoot: \"console\",\n enabled: true,\n requiresAuth: true,\n requiresWorkspace: false,\n accessPolicyId: \"console_owner\",\n icon: \"mdi-console-network-outline\",\n showInSurfaceSwitchMenu: false,\n origin: \"\"\n};\n",
187
- "reason": "Register console surface definition once console-web is installed.",
188
- "category": "console-web",
189
- "id": "console-web-surface-config-console"
190
- },
191
- {
192
- "op": "append-text",
193
- "file": "src/placement.js",
194
- "position": "bottom",
195
- "skipIfContains": "id: \"console.web.profile.menu.console\"",
196
- "value": "\naddPlacement({\n id: \"console.web.profile.menu.console\",\n target: \"auth.profile-menu\",\n kind: \"link\",\n surfaces: [\"*\"],\n order: 600,\n props: {\n label: \"Go to console\",\n to: \"/console\",\n icon: \"mdi-console-network-outline\"\n },\n when: ({ auth, surfaceAccess, surface }) => {\n return auth?.authenticated === true && surfaceAccess?.consoleowner === true && surface !== \"console\";\n }\n});\n",
197
- "reason": "Append owner-only console navigation entry into the authenticated profile menu outside the console surface.",
198
- "category": "console-web",
199
- "id": "console-web-profile-menu-console-placement"
200
- },
201
- {
202
- "op": "append-text",
203
- "file": "src/placement.js",
204
- "position": "bottom",
205
- "skipIfContains": "id: \"console.web.menu.settings\"",
206
- "value": "\naddPlacement({\n id: \"console.web.menu.settings\",\n target: \"shell.primary-nav\",\n kind: \"link\",\n surfaces: [\"console\"],\n order: 100,\n props: {\n label: \"Settings\",\n to: \"/console/settings\",\n icon: \"mdi-cog-outline\"\n },\n when: ({ auth }) => auth?.authenticated === true\n});\n",
207
- "reason": "Append console-web settings menu placement into app-owned placement registry.",
208
- "category": "console-web",
209
- "id": "console-web-console-settings-placement"
210
- },
211
- {
212
- "op": "append-text",
213
- "file": "src/placementTopology.js",
214
- "position": "bottom",
215
- "skipIfContains": "owner: \"console-settings\"",
216
- "value": "\naddPlacementTopology({\n id: \"page.section-nav\",\n owner: \"console-settings\",\n description: \"Navigation between console settings child pages.\",\n surfaces: [\"console\"],\n variants: {\n compact: {\n outlet: \"console-settings:primary-menu\",\n renderers: {\n link: \"local.main.ui.surface-aware-menu-link-item\"\n }\n },\n medium: {\n outlet: \"console-settings:primary-menu\",\n renderers: {\n link: \"local.main.ui.surface-aware-menu-link-item\"\n }\n },\n expanded: {\n outlet: \"console-settings:primary-menu\",\n renderers: {\n link: \"local.main.ui.surface-aware-menu-link-item\"\n }\n }\n }\n});\n",
217
- "reason": "Append console settings semantic topology into app-owned placement topology.",
218
- "category": "console-web",
219
- "id": "console-web-settings-placement-topology"
220
- }
221
- ]
222
110
  }
223
111
  }
224
112
  }
@@ -0,0 +1,58 @@
1
+ ---
2
+ id: console/console-surface
3
+ title: Owner console surface
4
+ summary: Add a protected administration surface, settings shell, and semantic navigation through JSKIT console and shell APIs.
5
+ keywords: admin, console, navigation, owner, placement, settings, surface
6
+ requires: @jskit-ai/console-core, @jskit-ai/console-web, @jskit-ai/shell-web
7
+ ---
8
+
9
+ # Owner console surface
10
+
11
+ ## Use when
12
+
13
+ Use this pattern for a separate owner-only or operator-only application surface.
14
+
15
+ ## Do not use when
16
+
17
+ Do not create a separate console merely for one settings page or when normal
18
+ workspace roles already express the required access boundary.
19
+
20
+ ## Product decisions
21
+
22
+ Choose who can enter the console, its route and label, which settings sections
23
+ it owns, and how authorized users switch to and from it.
24
+
25
+ ## Framework APIs
26
+
27
+ Use `@jskit-ai/console-core` for console capabilities,
28
+ `@jskit-ai/console-web` for the web provider, and JSKIT shell surface,
29
+ placement, and topology APIs for application composition.
30
+
31
+ ## Invariants
32
+
33
+ - Server policy enforces console access before application actions run.
34
+ - The surface definition and route pages use the same surface id.
35
+ - The profile switch is visible only to authorized authenticated users.
36
+ - Settings navigation uses semantic placements and responsive topology.
37
+ - Loading screens use skeletons and do not shift the surrounding shell.
38
+
39
+ ## Example files
40
+
41
+ `example/` contains concrete console root, landing, and settings routes. Adapt
42
+ them together with the surface policy and placement examples described here.
43
+
44
+ ## Variation points
45
+
46
+ Change the surface id, access policy, settings hierarchy, routes, labels, icons,
47
+ and switch placement to fit the product's administration model.
48
+
49
+ ## Verification
50
+
51
+ Test forbidden and authorized entry, switching between surfaces, direct route
52
+ loads, compact navigation, settings navigation, and production build output.
53
+
54
+ ## Avoid
55
+
56
+ - treating a hidden navigation item as authorization
57
+ - sharing normal-user pages merely by changing their route prefix
58
+ - generator ownership or appended source fragments
@@ -1,177 +1,32 @@
1
1
  import assert from "node:assert/strict";
2
+ import { readFile } from "node:fs/promises";
2
3
  import path from "node:path";
3
4
  import test from "node:test";
4
- import { readFile } from "node:fs/promises";
5
5
  import { fileURLToPath } from "node:url";
6
6
  import packageJson from "../package.json" with { type: "json" };
7
7
 
8
- const packageMetadata = packageJson.jskit;
9
-
10
- const TEST_DIRECTORY = path.dirname(fileURLToPath(import.meta.url));
11
- const PACKAGE_DIR = path.resolve(TEST_DIRECTORY, "..");
12
-
13
- function findFileMutation(id) {
14
- const files = packageMetadata?.mutations?.files;
15
- return Array.isArray(files)
16
- ? files.find((entry) => String(entry?.id || "").trim() === id) || null
17
- : null;
18
- }
19
-
20
- function findTextMutation(id) {
21
- const text = packageMetadata?.mutations?.text;
22
- return Array.isArray(text)
23
- ? text.find((entry) => String(entry?.id || "").trim() === id) || null
24
- : null;
25
- }
26
-
27
- function readOutlets(target = "") {
28
- const outlets = packageMetadata?.metadata?.ui?.placements?.outlets;
29
- const normalizedTarget = String(target || "").trim();
30
- return Array.isArray(outlets)
31
- ? outlets.filter((entry) => String(entry?.target || "").trim() === normalizedTarget)
32
- : [];
33
- }
34
-
35
- function findTopology(id, owner = "") {
36
- const placements = packageMetadata?.metadata?.ui?.placements?.topology?.placements;
37
- const normalizedId = String(id || "").trim();
38
- const normalizedOwner = String(owner || "").trim();
39
- return Array.isArray(placements)
40
- ? placements.find((entry) => {
41
- const entryId = String(entry?.id || "").trim();
42
- const entryOwner = String(entry?.owner || "").trim();
43
- return entryId === normalizedId && entryOwner === normalizedOwner;
44
- }) || null
45
- : null;
46
- }
8
+ const packageRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
47
9
 
48
- function findContribution(id) {
49
- const contributions = packageMetadata?.metadata?.ui?.placements?.contributions;
50
- return Array.isArray(contributions)
51
- ? contributions.find((entry) => String(entry?.id || "").trim() === id) || null
52
- : null;
53
- }
10
+ test("console-web publishes a pattern-owned console surface without source mutations", async () => {
11
+ const pattern = await readFile(path.join(packageRoot, "patterns/console-surface/PATTERN.md"), "utf8");
12
+ const page = await readFile(path.join(packageRoot, "patterns/console-surface/example/src/pages/console.vue"), "utf8");
13
+ const settings = await readFile(
14
+ path.join(packageRoot, "patterns/console-surface/example/src/pages/console/settings.vue"),
15
+ "utf8"
16
+ );
54
17
 
55
- test("console-web installs console surface scripts and files", () => {
56
- assert.deepEqual(packageMetadata?.mutations?.packageJson?.scripts, {
57
- "server:console": "SERVER_SURFACE=console node ./bin/server.js",
58
- "dev:console": "VITE_SURFACE=console vite",
59
- "build:console": "VITE_SURFACE=console vite build"
60
- });
61
- assert.deepEqual(findFileMutation("console-web-page-console-wrapper"), {
62
- from: "templates/src/pages/console.vue",
63
- toSurface: "console",
64
- toSurfaceRoot: true,
65
- ownership: "app",
66
- reason: "Install shell-driven console wrapper page.",
67
- category: "console-web",
68
- id: "console-web-page-console-wrapper"
69
- });
70
- assert.deepEqual(findFileMutation("console-web-page-console"), {
71
- from: "templates/src/pages/console/index.vue",
72
- toSurface: "console",
73
- toSurfacePath: "index.vue",
74
- ownership: "app",
75
- reason: "Install shell-driven console page starter.",
76
- category: "console-web",
77
- id: "console-web-page-console"
78
- });
79
- assert.deepEqual(findFileMutation("console-web-page-console-settings-shell"), {
80
- from: "templates/src/pages/console/settings.vue",
81
- toSurface: "console",
82
- toSurfacePath: "settings.vue",
83
- ownership: "app",
84
- reason: "Install console settings shell route scaffold for console-web.",
85
- category: "console-web",
86
- id: "console-web-page-console-settings-shell"
87
- });
88
- assert.deepEqual(findFileMutation("console-web-page-console-settings"), {
89
- from: "templates/src/pages/console/settings/index.vue",
90
- toSurface: "console",
91
- toSurfacePath: "settings/index.vue",
92
- ownership: "app",
93
- reason: "Install console settings index stub scaffold for app-owned landing or redirect behavior.",
94
- category: "console-web",
95
- id: "console-web-page-console-settings"
96
- });
18
+ assert.equal(packageJson.jskit.mutations, undefined);
19
+ assert.match(pattern, /semantic navigation/u);
20
+ assert.match(pattern, /Loading screens use skeletons/u);
21
+ assert.match(page, /ShellLayout/u);
22
+ assert.match(settings, /console-settings:primary-menu/u);
97
23
  });
98
24
 
99
- test("console-web wires console surface policy into app config", () => {
100
- assert.equal(
101
- findTextMutation("console-web-surface-access-policies-console-owner")?.file,
102
- "config/surfaceAccessPolicies.js"
103
- );
104
- assert.equal(findTextMutation("console-web-surface-config-console")?.file, "config/public.js");
105
- assert.match(findTextMutation("console-web-surface-config-console")?.value || "", /accessPolicyId: "console_owner"/);
106
- assert.match(findTextMutation("console-web-surface-config-console")?.value || "", /icon: "mdi-console-network-outline"/);
107
- assert.match(findTextMutation("console-web-surface-config-console")?.value || "", /showInSurfaceSwitchMenu: false/);
108
- assert.equal(findTextMutation("console-web-profile-menu-console-placement")?.file, "src/placement.js");
109
- assert.match(findTextMutation("console-web-profile-menu-console-placement")?.value || "", /label: "Go to console"/);
110
- assert.match(
111
- findTextMutation("console-web-profile-menu-console-placement")?.value || "",
112
- /surfaceAccess\?\.consoleowner === true && surface !== "console"/
113
- );
114
- assert.equal(findTextMutation("console-web-console-settings-placement")?.file, "src/placement.js");
115
- assert.equal(findTextMutation("console-web-settings-placement-topology")?.file, "src/placementTopology.js");
116
- assert.deepEqual(readOutlets("console-settings:primary-menu"), [
117
- {
118
- target: "console-settings:primary-menu",
119
- surfaces: ["console"],
120
- source: "templates/src/pages/console/settings.vue"
121
- }
122
- ]);
123
- assert.deepEqual(findTopology("page.section-nav", "console-settings"), {
124
- id: "page.section-nav",
125
- owner: "console-settings",
126
- description: "Navigation between console settings child pages.",
127
- surfaces: ["console"],
128
- variants: {
129
- compact: {
130
- outlet: "console-settings:primary-menu",
131
- renderers: {
132
- link: "local.main.ui.surface-aware-menu-link-item"
133
- }
134
- },
135
- medium: {
136
- outlet: "console-settings:primary-menu",
137
- renderers: {
138
- link: "local.main.ui.surface-aware-menu-link-item"
139
- }
140
- },
141
- expanded: {
142
- outlet: "console-settings:primary-menu",
143
- renderers: {
144
- link: "local.main.ui.surface-aware-menu-link-item"
145
- }
146
- }
147
- }
148
- });
149
- assert.deepEqual(findContribution("console.web.menu.settings"), {
150
- id: "console.web.menu.settings",
151
- target: "shell.primary-nav",
152
- kind: "link",
25
+ test("console-web metadata describes its semantic settings outlet", () => {
26
+ const outlets = packageJson.jskit.metadata.ui.placements.outlets;
27
+ assert.deepEqual(outlets, [{
28
+ target: "console-settings:primary-menu",
153
29
  surfaces: ["console"],
154
- order: 100,
155
- when: "auth.authenticated === true",
156
- source: "mutations.text#console-web-console-settings-placement"
157
- });
158
- });
159
-
160
- test("console-web console templates stay shell-driven", async () => {
161
- const wrapperSource = await readFile(path.join(PACKAGE_DIR, "templates", "src", "pages", "console.vue"), "utf8");
162
- const indexSource = await readFile(path.join(PACKAGE_DIR, "templates", "src", "pages", "console", "index.vue"), "utf8");
163
- const settingsSource = await readFile(path.join(PACKAGE_DIR, "templates", "src", "pages", "console", "settings.vue"), "utf8");
164
- const settingsIndexSource = await readFile(path.join(PACKAGE_DIR, "templates", "src", "pages", "console", "settings", "index.vue"), "utf8");
165
-
166
- assert.match(wrapperSource, /ShellLayout/);
167
- assert.match(wrapperSource, /"surface": "console"/);
168
- assert.match(indexSource, /Operations Console/);
169
- assert.match(indexSource, /console-home-screen__panel/);
170
- assert.doesNotMatch(indexSource, /<v-card\b|v-card-title|v-card-subtitle/);
171
- assert.match(settingsSource, /target="console-settings:primary-menu"/);
172
- assert.doesNotMatch(settingsSource, /default-link-component-token/);
173
- assert.doesNotMatch(settingsSource, /<v-card\b/);
174
- assert.match(settingsSource, /<RouterView \/>/);
175
- assert.match(settingsIndexSource, /No console settings yet/);
176
- assert.doesNotMatch(settingsIndexSource, /your_child_segment|To redirect this settings shell/);
30
+ source: "patterns/console-surface/example/src/pages/console/settings.vue"
31
+ }]);
177
32
  });