Skip to content

Commit ed61e63

Browse files
author
DavidQ
committed
Add launch-mode-specific NAV behavior to first-class tool template - PR_26126_068-tool-template-launch-mode-nav
1 parent 0f067a9 commit ed61e63

10 files changed

Lines changed: 265 additions & 50 deletions

File tree

docs/dev/reports/playwright_v8_coverage_report.txt

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,15 @@ Note: coverage entries are aggregated across every page/tool where coverageRepor
1313
Exercised tool entry points detected:
1414
(84%) Preview Generator V2 - exercised 19 runtime JS files
1515
(49%) Palette Manager - exercised 12 runtime JS files
16-
(89%) First-Class Tool Starter Template - exercised 10 runtime JS files
16+
(84%) First-Class Tool Starter Template - exercised 10 runtime JS files
1717
(0%) Workspace V2 - not exercised by this Playwright run
1818
(0%) Workspace Manager - not exercised by this Playwright run
1919

2020
Changed runtime JS files covered:
21-
(100%) none changed - no changed runtime JS files
21+
(50%) tools/templates/first-class-tool-starter/js/ToolStarterApp.js - executed lines 98/98; executed functions 8/16
22+
(88%) tools/templates/first-class-tool-starter/js/controls/SourceInputControl.js - executed lines 33/33; executed functions 7/8
23+
(100%) tools/templates/first-class-tool-starter/js/bootstrap.js - executed lines 48/48; executed functions 4/4
24+
(100%) tools/templates/first-class-tool-starter/js/controls/ActionNavControl.js - executed lines 51/51; executed functions 5/5
2225

2326
Files with executed line/function counts where available:
2427
(2%) src/engine/input/ActionInputService.js - executed lines 397/397; executed functions 1/51
@@ -85,6 +88,7 @@ Files with executed line/function counts where available:
8588
(50%) tools/common/PaletteUsageService.js - executed lines 5/5; executed functions 1/2
8689
(50%) tools/palette-manager-v2/main.js - executed lines 88/88; executed functions 3/6
8790
(50%) tools/palette-manager-v2/modules/PaletteHistoryStack.js - executed lines 54/54; executed functions 5/10
91+
(50%) tools/templates/first-class-tool-starter/js/ToolStarterApp.js - executed lines 98/98; executed functions 8/16
8892
(50%) tools/toolRegistry.js - executed lines 425/425; executed functions 4/8
8993
(55%) tools/palette-manager-v2/modules/SwatchRow.js - executed lines 115/115; executed functions 6/11
9094
(56%) src/engine/logging/Logger.js - executed lines 56/56; executed functions 5/9
@@ -97,7 +101,6 @@ Files with executed line/function counts where available:
97101
(65%) tools/palette-manager-v2/controls/SourcePaletteBrowserControl.js - executed lines 121/121; executed functions 15/23
98102
(67%) src/shared/number/numberUtils.js - executed lines 14/14; executed functions 2/3
99103
(67%) tools/preview-generator-v2/PreviewGeneratorV2ShellControl.js - executed lines 117/117; executed functions 8/12
100-
(67%) tools/templates/first-class-tool-starter/js/ToolStarterApp.js - executed lines 69/69; executed functions 8/12
101104
(71%) tools/palette-manager-v2/controls/PaletteValidationErrorControl.js - executed lines 30/30; executed functions 5/7
102105
(73%) src/engine/theme/mount-shared-header.js - executed lines 143/143; executed functions 8/11
103106
(75%) src/engine/core/FixedTicker.js - executed lines 36/36; executed functions 3/4
@@ -146,15 +149,20 @@ Files with executed line/function counts where available:
146149
(100%) tools/preview-generator-v2/PreviewGeneratorV2Logger.js - executed lines 19/19; executed functions 5/5
147150
(100%) tools/preview-generator-v2/PreviewGeneratorV2RepoAccess.js - executed lines 21/21; executed functions 5/5
148151
(100%) tools/preview-generator-v2/PreviewGeneratorV2Ui.js - executed lines 48/48; executed functions 9/9
149-
(100%) tools/templates/first-class-tool-starter/js/bootstrap.js - executed lines 42/42; executed functions 4/4
152+
(100%) tools/templates/first-class-tool-starter/js/bootstrap.js - executed lines 48/48; executed functions 4/4
150153
(100%) tools/templates/first-class-tool-starter/js/controls/AccordionSection.js - executed lines 27/27; executed functions 5/5
151-
(100%) tools/templates/first-class-tool-starter/js/controls/ActionNavControl.js - executed lines 16/16; executed functions 4/4
154+
(100%) tools/templates/first-class-tool-starter/js/controls/ActionNavControl.js - executed lines 51/51; executed functions 5/5
152155
(100%) tools/templates/first-class-tool-starter/js/controls/InspectorControl.js - executed lines 8/8; executed functions 3/3
153156
(100%) tools/templates/first-class-tool-starter/js/controls/PreviewPanelControl.js - executed lines 23/23; executed functions 5/5
154157
(100%) tools/templates/first-class-tool-starter/js/services/ToolStateSerializer.js - executed lines 13/13; executed functions 3/3
155158

156159
Uncovered or low-coverage changed JS files:
157-
(100%) none changed - no changed runtime JS files
160+
(100%) none - no low-coverage changed runtime JS files
158161

159162
Changed JS files considered:
160-
(100%) none - no changed JS files
163+
(0%) tests/playwright/PreviewGeneratorV2Baseline.spec.mjs - changed JS file not collected as browser runtime coverage
164+
(0%) tools/templates/first-class-tool-starter/tests/playwright/FirstClassToolStarter.spec.mjs - changed JS file not collected as browser runtime coverage
165+
(50%) tools/templates/first-class-tool-starter/js/ToolStarterApp.js - changed JS file with browser V8 coverage
166+
(88%) tools/templates/first-class-tool-starter/js/controls/SourceInputControl.js - changed JS file with browser V8 coverage
167+
(100%) tools/templates/first-class-tool-starter/js/bootstrap.js - changed JS file with browser V8 coverage
168+
(100%) tools/templates/first-class-tool-starter/js/controls/ActionNavControl.js - changed JS file with browser V8 coverage
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
PR_26126_068-tool-template-launch-mode-nav
2+
3+
Scope:
4+
- Updated the official first-class tool starter at tools/templates/first-class-tool-starter/.
5+
- Updated existing Playwright coverage for the starter launch-mode NAV behavior.
6+
- No live tools, samples, schemas, roadmap, start_of_day folders, or tools/shared files were modified.
7+
8+
Changes:
9+
- Added separate tool-mode and workspace-mode NAV sections.
10+
- Default launch mode is tool mode.
11+
- ?launch=workspace switches visibility to the workspace NAV.
12+
- Tool NAV uses class tool-starter__tool__menu, aria-label Tool actions, and buttons:
13+
- Export
14+
- Copy JSON
15+
- Export toolState
16+
- Workspace NAV uses class tool-starter__workspace__menu, aria-label Workspace actions, and buttons:
17+
- Import manifest
18+
- Copy manifest
19+
- Export manifest
20+
- Added hidden-state CSS so only one NAV displays at a time.
21+
- Kept Preview Generator V2 color-match styling and src/engine/theme usage.
22+
- Preserved no tools/shared runtime dependency.
23+
- Preserved external CSS/JS only; no inline style block, inline script block, or inline event handlers were added.
24+
25+
Validation:
26+
- node --check tools/templates/first-class-tool-starter/js/controls/ActionNavControl.js
27+
- node --check tools/templates/first-class-tool-starter/js/ToolStarterApp.js
28+
- node --check tools/templates/first-class-tool-starter/js/bootstrap.js
29+
- node --check tools/templates/first-class-tool-starter/js/controls/SourceInputControl.js
30+
- node --check tests/playwright/PreviewGeneratorV2Baseline.spec.mjs
31+
- node --check tools/templates/first-class-tool-starter/tests/playwright/FirstClassToolStarter.spec.mjs
32+
- rg check confirmed tools/templates/first-class-tool-starter/index.html has no inline style block, inline script block, or inline event handlers.
33+
- git diff --check
34+
- npm run test:workspace-v2
35+
- npx playwright test --config tools/templates/first-class-tool-starter/playwright.config.mjs --reporter=list
36+
37+
Playwright impacted: Yes
38+
- This PR changes template UI controls/interactions and launch-mode state.
39+
- npm run test:workspace-v2 passed.
40+
- Template-local Playwright passed.
41+
42+
Playwright behavior validated:
43+
- Official starter entry point defaults to tool NAV.
44+
- Official starter entry point with ?launch=workspace shows workspace NAV.
45+
- Only one launch-mode NAV is visible for each launch mode.
46+
- Tool and workspace NAV labels and button text match the required casing.
47+
- Duplicate button IDs are not present.
48+
- Existing starter accordion, required-field gating, primary action, and status clear behaviors still work.
49+
50+
Manual test notes:
51+
- Open tools/templates/first-class-tool-starter/index.html and confirm only Tool actions is visible with Export, Copy JSON, and Export toolState.
52+
- Open tools/templates/first-class-tool-starter/index.html?launch=workspace and confirm only Workspace actions is visible with Import manifest, Copy manifest, and Export manifest.
53+
- The official starter path is tools/templates/first-class-tool-starter/index.html; tools/templates/README.md designates that folder as the starter entry point.
54+
55+
Full samples smoke test:
56+
- Skipped. This PR only changes the first-class tool starter template and its targeted tests.

tests/playwright/PreviewGeneratorV2Baseline.spec.mjs

Lines changed: 47 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -120,10 +120,10 @@ async function openPaletteManager(page) {
120120
return server;
121121
}
122122

123-
async function openToolTemplate(page) {
123+
async function openToolTemplate(page, query = "") {
124124
const server = await startRepoServer();
125125
await coverageReporter.start(page);
126-
await page.goto(`${server.baseUrl}/tools/templates/first-class-tool-starter/index.html`, { waitUntil: "networkidle" });
126+
await page.goto(`${server.baseUrl}/tools/templates/first-class-tool-starter/index.html${query}`, { waitUntil: "networkidle" });
127127
return server;
128128
}
129129

@@ -466,6 +466,18 @@ test.describe("Preview Generator V2 baseline", () => {
466466
await expect(page.locator("#shared-theme-header")).toBeAttached();
467467
await expect(page.locator("[data-tool-starter-header]")).toContainText("First-Class Tool Starter");
468468
await expect(page.locator("[data-tool-starter-summary]")).toHaveAttribute("data-tools-platform-summary-active", "1");
469+
await expect(page.locator(".tool-starter__tool__menu")).toBeVisible();
470+
await expect(page.locator(".tool-starter__tool__menu")).toHaveAttribute("aria-label", "Tool actions");
471+
await expect(page.locator(".tool-starter__workspace__menu")).toBeHidden();
472+
await expect(page.locator("#toolExportButton")).toHaveText("Export");
473+
await expect(page.locator("#toolCopyJsonButton")).toHaveText("Copy JSON");
474+
await expect(page.locator("#toolExportToolStateButton")).toHaveText("Export toolState");
475+
476+
const duplicateIds = await page.evaluate(() => {
477+
const ids = [...document.querySelectorAll("[id]")].map((element) => element.id);
478+
return ids.filter((id, index) => ids.indexOf(id) !== index);
479+
});
480+
expect(duplicateIds).toEqual([]);
469481

470482
const sharedReferences = await page.evaluate(() => [
471483
...document.querySelectorAll("script[src],link[href]")
@@ -482,17 +494,23 @@ test.describe("Preview Generator V2 baseline", () => {
482494

483495
await expectAccordionToggles(page, "sourceInputContent");
484496

485-
const runButton = page.locator("#runToolButton");
486-
await expect(runButton).toBeDisabled();
497+
const exportButton = page.locator("#toolExportButton");
498+
const copyJsonButton = page.locator("#toolCopyJsonButton");
499+
const exportToolStateButton = page.locator("#toolExportToolStateButton");
500+
await expect(exportButton).toBeDisabled();
501+
await expect(copyJsonButton).toBeDisabled();
502+
await expect(exportToolStateButton).toBeDisabled();
487503
await page.locator("#sourceInput").fill("starter value");
488-
await expect(runButton).toBeEnabled();
489-
await runButton.click();
504+
await expect(exportButton).toBeEnabled();
505+
await expect(copyJsonButton).toBeEnabled();
506+
await expect(exportToolStateButton).toBeEnabled();
507+
await exportButton.click();
490508
await expect(page.locator("#statusLog")).toHaveValue(/Processed source value/);
491509
await page.locator("#clearStatusButton").click();
492510
await expect(page.locator("#statusLog")).toHaveValue("");
493511

494512
await page.locator("#sourceInput").fill("");
495-
await expect(runButton).toBeDisabled();
513+
await expect(exportButton).toBeDisabled();
496514
await expect(page.locator("#sourceValidationMessage")).toContainText("Input is required");
497515

498516
expect(pageErrors).toEqual([]);
@@ -501,4 +519,26 @@ test.describe("Preview Generator V2 baseline", () => {
501519
await server.close();
502520
}
503521
});
522+
523+
test("launches first-class tool starter template in workspace nav mode", async ({ page }) => {
524+
const server = await openToolTemplate(page, "?launch=workspace");
525+
const pageErrors = [];
526+
527+
page.on("pageerror", (error) => {
528+
pageErrors.push(error.message);
529+
});
530+
531+
try {
532+
await expect(page.locator(".tool-starter__tool__menu")).toBeHidden();
533+
await expect(page.locator(".tool-starter__workspace__menu")).toBeVisible();
534+
await expect(page.locator(".tool-starter__workspace__menu")).toHaveAttribute("aria-label", "Workspace actions");
535+
await expect(page.locator("#workspaceImportManifestButton")).toHaveText("Import manifest");
536+
await expect(page.locator("#workspaceCopyManifestButton")).toHaveText("Copy manifest");
537+
await expect(page.locator("#workspaceExportManifestButton")).toHaveText("Export manifest");
538+
expect(pageErrors).toEqual([]);
539+
} finally {
540+
await coverageReporter.stop(page);
541+
await server.close();
542+
}
543+
});
504544
});

tools/templates/first-class-tool-starter/index.html

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -36,10 +36,16 @@ <h2 class="tools-platform-frame__eyebrow">First-Class Tools Surface</h2>
3636
</div>
3737
</details>
3838

39-
<nav class="tool-starter__menu" aria-label="Tool actions">
40-
<button id="runToolButton" type="button">Run</button>
41-
<button id="resetToolButton" type="button">Reset</button>
42-
<button id="exportToolStateButton" type="button">Export toolState</button>
39+
<nav class="tool-starter__menu tool-starter__tool__menu" aria-label="Tool actions" data-launch-mode-nav="tool">
40+
<button id="toolExportButton" type="button">Export</button>
41+
<button id="toolCopyJsonButton" type="button">Copy JSON</button>
42+
<button id="toolExportToolStateButton" type="button">Export toolState</button>
43+
</nav>
44+
45+
<nav class="tool-starter__menu tool-starter__workspace__menu" aria-label="Workspace actions" data-launch-mode-nav="workspace" hidden>
46+
<button id="workspaceImportManifestButton" type="button">Import manifest</button>
47+
<button id="workspaceCopyManifestButton" type="button">Copy manifest</button>
48+
<button id="workspaceExportManifestButton" type="button">Export manifest</button>
4349
</nav>
4450

4551
<main class="tool-starter app-shell" data-tool-id="first-class-tool-starter">
@@ -54,7 +60,7 @@ <h2 class="tools-platform-frame__eyebrow">First-Class Tools Surface</h2>
5460
<span>Source value</span>
5561
<input id="sourceInput" type="text" autocomplete="off" placeholder="Enter a value to process">
5662
</label>
57-
<p id="sourceValidationMessage" class="tool-starter__hint">Input is required before Run can process.</p>
63+
<p id="sourceValidationMessage" class="tool-starter__hint">Input is required before Export can process.</p>
5864
</div>
5965
</section>
6066

tools/templates/first-class-tool-starter/js/ToolStarterApp.js

Lines changed: 38 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
export class ToolStarterApp {
2-
constructor({ accordions, actionNav, inspector, preview, serializer, shell, sourceInput, statusLog }) {
2+
constructor({ accordions, actionNav, inspector, preview, serializer, shell, sourceInput, statusLog, windowRef = window }) {
33
this.accordions = accordions;
44
this.actionNav = actionNav;
55
this.inspector = inspector;
@@ -8,15 +8,21 @@ export class ToolStarterApp {
88
this.shell = shell;
99
this.sourceInput = sourceInput;
1010
this.statusLog = statusLog;
11+
this.window = windowRef;
1112
}
1213

1314
start() {
1415
this.shell.mount();
1516
this.accordions.forEach((accordion) => accordion.mount());
1617
this.actionNav.mount({
17-
onExport: () => this.exportToolState(),
18-
onReset: () => this.reset(),
19-
onRun: () => this.run()
18+
onToolCopyJson: () => {
19+
void this.copyJson();
20+
},
21+
onToolExport: () => this.run(),
22+
onToolExportToolState: () => this.exportToolState(),
23+
onWorkspaceCopyManifest: () => this.statusLog.write("Copy manifest action ready for workspace wiring."),
24+
onWorkspaceExportManifest: () => this.statusLog.write("Export manifest action ready for workspace wiring."),
25+
onWorkspaceImportManifest: () => this.statusLog.write("Import manifest action ready for workspace wiring.")
2026
});
2127
this.sourceInput.mount({
2228
onChange: () => this.refreshActions()
@@ -66,11 +72,37 @@ export class ToolStarterApp {
6672
this.refreshActions();
6773
}
6874

75+
async copyJson() {
76+
const validation = this.sourceInput.validate();
77+
if (!validation.valid) {
78+
this.statusLog.error(validation.message);
79+
this.refreshActions();
80+
return;
81+
}
82+
83+
const toolState = this.serializer.createToolState({ sourceValue: validation.value });
84+
this.inspector.showObject(toolState);
85+
const json = JSON.stringify(toolState, null, 2);
86+
if (typeof this.window.navigator?.clipboard?.writeText !== "function") {
87+
this.statusLog.write("toolState JSON preview written to Output Summary. Clipboard API is unavailable.");
88+
this.refreshActions();
89+
return;
90+
}
91+
92+
try {
93+
await this.window.navigator.clipboard.writeText(json);
94+
this.statusLog.write("toolState JSON copied.");
95+
} catch (error) {
96+
this.statusLog.error(`Copy JSON failed: ${error.message}`);
97+
}
98+
this.refreshActions();
99+
}
100+
69101
refreshActions() {
70102
const hasValue = this.sourceInput.hasValue();
71103
if (!hasValue) {
72-
this.sourceInput.showMessage("Input is required before Run can process.", false);
104+
this.sourceInput.showMessage("Input is required before Export can process.", false);
73105
}
74-
this.actionNav.setRunEnabled(hasValue);
106+
this.actionNav.setToolActionsEnabled(hasValue);
75107
}
76108
}

tools/templates/first-class-tool-starter/js/bootstrap.js

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,16 +29,22 @@ window.addEventListener("DOMContentLoaded", () => {
2929
const app = new ToolStarterApp({
3030
accordions,
3131
actionNav: new ActionNavControl({
32-
runButton: requireElement("#runToolButton"),
33-
resetButton: requireElement("#resetToolButton"),
34-
exportButton: requireElement("#exportToolStateButton")
32+
toolCopyJsonButton: requireElement("#toolCopyJsonButton"),
33+
toolExportButton: requireElement("#toolExportButton"),
34+
toolExportToolStateButton: requireElement("#toolExportToolStateButton"),
35+
toolNav: requireElement(".tool-starter__tool__menu"),
36+
workspaceCopyManifestButton: requireElement("#workspaceCopyManifestButton"),
37+
workspaceExportManifestButton: requireElement("#workspaceExportManifestButton"),
38+
workspaceImportManifestButton: requireElement("#workspaceImportManifestButton"),
39+
workspaceNav: requireElement(".tool-starter__workspace__menu")
3540
}),
3641
inspector: new InspectorControl(requireElement("#inspectorOutput")),
3742
preview: new PreviewPanelControl(requireElement("#previewOutput")),
3843
serializer: new ToolStateSerializer("first-class-tool-starter"),
3944
shell: new ToolStarterShellControl(),
4045
sourceInput,
41-
statusLog
46+
statusLog,
47+
windowRef: window
4248
});
4349

4450
app.start();

0 commit comments

Comments
 (0)