|
1 | 1 | diff --git a/docs/dev/codex_commands.md b/docs/dev/codex_commands.md |
2 | | -index a8fc23b5..ae8b5047 100644 |
| 2 | +index ae8b5047..462ea4db 100644 |
3 | 3 | --- a/docs/dev/codex_commands.md |
4 | 4 | +++ b/docs/dev/codex_commands.md |
5 | | -@@ -1,14 +1,17 @@ |
6 | | --# Codex Commands - PR_26124_075-palette-browser-launch-registration-fix |
7 | | -+# Codex Commands - PR_26124_076-palette-manager-url-preset-load |
| 5 | +@@ -1,16 +1,17 @@ |
| 6 | +-# Codex Commands - PR_26124_076-palette-manager-url-preset-load |
| 7 | ++# Codex Commands - PR_26124_077-palette-manager-final-exit-pass |
8 | 8 |
|
9 | 9 | ```bash |
10 | | --npx @openai/codex run --model gpt-5.5 --reasoning high "Run full workflow for PR_26124_075-palette-browser-launch-registration-fix. Follow PROJECT_INSTRUCTIONS.md exactly." |
11 | | -+npx @openai/codex run --model gpt-5.5 --reasoning high "Run full workflow for PR_26124_076-palette-manager-url-preset-load. Follow PROJECT_INSTRUCTIONS.md exactly." |
| 10 | +-npx @openai/codex run --model gpt-5.5 --reasoning high "Run full workflow for PR_26124_076-palette-manager-url-preset-load. Follow PROJECT_INSTRUCTIONS.md exactly." |
| 11 | ++npx @openai/codex run --model gpt-5.5 --reasoning high "Run full workflow for PR_26124_077-palette-manager-final-exit-pass. Follow PROJECT_INSTRUCTIONS.md exactly." |
12 | 12 | ``` |
13 | 13 |
|
14 | 14 | ## Validation Commands |
15 | 15 |
|
16 | 16 | ```bash |
17 | | --node --input-type=module <sample metadata registry validation> |
18 | | --node --input-type=module <targeted Samples index launch validation> |
19 | | -+node --check tools/palette-manager-v2/main.js |
20 | | -+node --check tools/palette-manager-v2/modules/PaletteManagerApp.js |
21 | | -+node --check tools/palette-manager-v2/modules/PaletteValidationService.js |
22 | | -+node tests/tools/PaletteManagerV2Baseline.test.mjs |
23 | | -+node --input-type=module <targeted Palette Manager V2 URL preset validation> |
| 17 | + node --check tools/palette-manager-v2/main.js |
| 18 | ++node --check tools/palette-manager-v2/paletteManagerShell.js |
| 19 | + node --check tools/palette-manager-v2/modules/PaletteManagerApp.js |
| 20 | +-node --check tools/palette-manager-v2/modules/PaletteValidationService.js |
| 21 | + node tests/tools/PaletteManagerV2Baseline.test.mjs |
| 22 | ++node --input-type=module <targeted Palette Manager V2 exit-pass audit> |
| 23 | + node --input-type=module <targeted Palette Manager V2 URL preset validation> |
24 | 24 | git diff --check |
25 | 25 | npm run test:workspace-v2 |
26 | | - npm run codex:review-artifacts |
27 | | -@@ -16,7 +19,7 @@ npm run codex:review-artifacts |
| 26 | +@@ -19,7 +20,7 @@ npm run codex:review-artifacts |
28 | 27 |
|
29 | 28 | ## Playwright |
30 | 29 |
|
31 | | --Targeted Samples index launch validation confirms palette-backed samples no longer render `Tool "palette-browser" is not registered in toolRegistry.` and their launch links resolve to Palette Manager V2. |
32 | | -+Targeted Palette Manager V2 validation confirms the tool baseline still loads and that a `samplePresetPath` URL loads sample palette JSON into active Palette Manager V2 state. |
| 30 | +-Targeted Palette Manager V2 validation confirms the tool baseline still loads and that a `samplePresetPath` URL loads sample palette JSON into active Palette Manager V2 state. |
| 31 | ++Targeted Palette Manager V2 validation confirms baseline controls, validation clear placement, pin scroll preservation, Tag sort untagged-last behavior, and URL preset loading remain stable. |
33 | 32 |
|
34 | 33 | `npm run test:workspace-v2` failed because `package.json` does not define the `test:workspace-v2` script. |
35 | 34 |
|
36 | 35 | diff --git a/docs/dev/commit_comment.txt b/docs/dev/commit_comment.txt |
37 | | -index fa01d587..aa8b5149 100644 |
| 36 | +index aa8b5149..83e847f4 100644 |
38 | 37 | --- a/docs/dev/commit_comment.txt |
39 | 38 | +++ b/docs/dev/commit_comment.txt |
40 | 39 | @@ -1 +1 @@ |
41 | | --Fix Palette Manager V2 sample launch registration id - PR_26124_075-palette-browser-launch-registration-fix |
42 | | -+Load Palette Manager V2 sample presets from URL params - PR_26124_076-palette-manager-url-preset-load |
43 | | -diff --git a/tools/palette-manager-v2/main.js b/tools/palette-manager-v2/main.js |
44 | | -index 52ef12b1..58ed92f3 100644 |
45 | | ---- a/tools/palette-manager-v2/main.js |
46 | | -+++ b/tools/palette-manager-v2/main.js |
47 | | -@@ -18,6 +18,72 @@ function reportBootstrapError(error) { |
48 | | - console.error(error); |
| 40 | +-Load Palette Manager V2 sample presets from URL params - PR_26124_076-palette-manager-url-preset-load |
| 41 | ++Finalize Palette Manager V2 exit-pass cleanup - PR_26124_077-palette-manager-final-exit-pass |
| 42 | +diff --git a/tools/palette-manager-v2/paletteManagerV2.css b/tools/palette-manager-v2/paletteManagerV2.css |
| 43 | +index 3c7ca292..a168a560 100644 |
| 44 | +--- a/tools/palette-manager-v2/paletteManagerV2.css |
| 45 | ++++ b/tools/palette-manager-v2/paletteManagerV2.css |
| 46 | +@@ -252,14 +252,6 @@ body.tools-platform-surface :is(input, select, textarea):hover { |
| 47 | + --accordion-v2-line: var(--pm-line); |
| 48 | + --accordion-v2-surface: var(--pm-surface); |
49 | 49 | } |
50 | | - |
51 | | -+function normalizeSamplePresetPath(samplePresetPath) { |
52 | | -+ const cleanPath = typeof samplePresetPath === "string" |
53 | | -+ ? samplePresetPath.trim().replace(/\\/g, "/") |
54 | | -+ : ""; |
55 | | -+ if (!cleanPath || cleanPath.includes("..") || !cleanPath.startsWith("/samples/")) { |
56 | | -+ return ""; |
57 | | -+ } |
58 | | -+ return cleanPath; |
59 | | -+} |
60 | | -+ |
61 | | -+function getSamplePresetLabel(searchParams, samplePresetPath) { |
62 | | -+ const sampleId = (searchParams.get("sampleId") || "").trim(); |
63 | | -+ const sampleTitle = (searchParams.get("sampleTitle") || "").trim(); |
64 | | -+ if (sampleId && sampleTitle) { |
65 | | -+ return `sample ${sampleId} (${sampleTitle})`; |
66 | | -+ } |
67 | | -+ if (sampleId) { |
68 | | -+ return `sample ${sampleId}`; |
69 | | -+ } |
70 | | -+ if (sampleTitle) { |
71 | | -+ return sampleTitle; |
72 | | -+ } |
73 | | -+ return samplePresetPath; |
74 | | -+} |
75 | | -+ |
76 | | -+async function loadSamplePresetFromUrl(app) { |
77 | | -+ const searchParams = new URLSearchParams(window.location.search); |
78 | | -+ const samplePresetPath = searchParams.get("samplePresetPath"); |
79 | | -+ if (!samplePresetPath) { |
80 | | -+ return; |
81 | | -+ } |
82 | | -+ |
83 | | -+ const presetPath = normalizeSamplePresetPath(samplePresetPath); |
84 | | -+ if (!presetPath) { |
85 | | -+ app.rejectImport([`samplePresetPath is invalid: ${samplePresetPath}`], "Sample preset load failed."); |
86 | | -+ return; |
87 | | -+ } |
88 | | -+ |
89 | | -+ let response; |
90 | | -+ try { |
91 | | -+ response = await fetch(presetPath, { cache: "no-store" }); |
92 | | -+ } catch (error) { |
93 | | -+ const message = error instanceof Error ? error.message : String(error); |
94 | | -+ app.rejectImport([`Sample preset fetch failed for ${presetPath}: ${message}`], "Sample preset load failed."); |
95 | | -+ return; |
96 | | -+ } |
97 | | -+ |
98 | | -+ if (!response.ok) { |
99 | | -+ app.rejectImport([`Sample preset fetch failed (${response.status}) for ${presetPath}.`], "Sample preset load failed."); |
100 | | -+ return; |
101 | | -+ } |
102 | | -+ |
103 | | -+ let documentValue; |
104 | | -+ try { |
105 | | -+ documentValue = await response.json(); |
106 | | -+ } catch { |
107 | | -+ app.rejectImport([`Sample preset is not valid JSON: ${presetPath}.`], "Sample preset load failed."); |
108 | | -+ return; |
109 | | -+ } |
110 | | -+ |
111 | | -+ app.importPaletteDocument(documentValue, { |
112 | | -+ failureStatus: "Sample preset load failed.", |
113 | | -+ successStatus: `Loaded ${getSamplePresetLabel(searchParams, presetPath)} palette preset.` |
114 | | -+ }); |
115 | | -+} |
116 | | -+ |
117 | | - try { |
118 | | - const app = new PaletteManagerApp({ |
119 | | - documentRef: document, |
120 | | -@@ -27,6 +93,7 @@ try { |
121 | | - }); |
122 | | - app.init(); |
123 | | - window.paletteManagerV2App = app.getPublicApi(); |
124 | | -+ void loadSamplePresetFromUrl(app); |
125 | | - } catch (error) { |
126 | | - reportBootstrapError(error); |
| 50 | +-/* |
| 51 | +- |
| 52 | +-.palette-manager-v2__right-accordion--import { |
| 53 | +- flex: 0 0 auto; |
| 54 | +- min-height: 0; |
| 55 | +- overflow: hidden; |
| 56 | +-} |
| 57 | +-*/ |
| 58 | + |
| 59 | + .palette-manager-v2__right-accordion--import.is-open { |
| 60 | + flex: 1 1 auto; |
| 61 | +@@ -267,14 +259,6 @@ body.tools-platform-surface :is(input, select, textarea):hover { |
| 62 | + overflow: hidden; |
127 | 63 | } |
128 | | -diff --git a/tools/palette-manager-v2/modules/PaletteManagerApp.js b/tools/palette-manager-v2/modules/PaletteManagerApp.js |
129 | | -index 3ac0824b..324ea23b 100644 |
130 | | ---- a/tools/palette-manager-v2/modules/PaletteManagerApp.js |
131 | | -+++ b/tools/palette-manager-v2/modules/PaletteManagerApp.js |
132 | | -@@ -998,15 +998,15 @@ export class PaletteManagerApp { |
133 | | - }); |
134 | | - } |
135 | | - |
136 | | -- rejectImport(errors) { |
137 | | -- this.setActionState(errors, "Import rejected."); |
138 | | -+ rejectImport(errors, status = "Import rejected.") { |
139 | | -+ this.setActionState(errors, status); |
140 | | - } |
141 | | - |
142 | | -- importPaletteDocument(documentValue) { |
143 | | -+ importPaletteDocument(documentValue, options = {}) { |
144 | | - const importResult = this.validator.extractImportedPaletteDocument(documentValue); |
145 | | - if (importResult.errors.length > 0) { |
146 | | -- this.setActionState(importResult.errors, "Import rejected."); |
147 | | -- return; |
148 | | -+ this.setActionState(importResult.errors, sanitizeText(options.failureStatus) || "Import rejected."); |
149 | | -+ return false; |
150 | | - } |
151 | | - |
152 | | - this.state.userSwatches = importResult.swatches.map(cloneSwatch); |
153 | | -@@ -1015,7 +1015,8 @@ export class PaletteManagerApp { |
154 | | - this.checkedUserSwatchIndexes.clear(); |
155 | | - this.editorControl.clearForm(); |
156 | | - this.resetHistorySnapshot(); |
157 | | -- this.setActionState([], `Imported ${this.state.userSwatches.length} user swatches.`); |
158 | | -+ this.setActionState([], sanitizeText(options.successStatus) || `Imported ${this.state.userSwatches.length} user swatches.`); |
159 | | -+ return true; |
160 | | - } |
161 | | - |
162 | | - preparePaletteDocument(blockedStatus) { |
163 | | -diff --git a/tools/palette-manager-v2/modules/PaletteValidationService.js b/tools/palette-manager-v2/modules/PaletteValidationService.js |
164 | | -index e8cc0aa9..ae8f0782 100644 |
165 | | ---- a/tools/palette-manager-v2/modules/PaletteValidationService.js |
166 | | -+++ b/tools/palette-manager-v2/modules/PaletteValidationService.js |
167 | | -@@ -52,13 +52,36 @@ export class PaletteValidationService { |
168 | | - return swatches.flatMap((swatch, index) => this.validateSwatch(swatch, `swatches[${index}]`)); |
169 | | - } |
170 | 64 |
|
171 | | -+ getDirectPaletteSource(documentValue) { |
172 | | -+ return sanitizeText(documentValue?.sourceId) |
173 | | -+ || sanitizeText(documentValue?.source) |
174 | | -+ || sanitizeText(documentValue?.name); |
175 | | -+ } |
176 | | -+ |
177 | | -+ cloneImportedSwatches(swatches, source) { |
178 | | -+ const cleanSource = sanitizeText(source); |
179 | | -+ return swatches.map((swatch) => cloneSwatch({ |
180 | | -+ ...swatch, |
181 | | -+ source: sanitizeText(swatch?.source) || cleanSource |
182 | | -+ })); |
183 | | -+ } |
184 | | -+ |
185 | | - extractImportedPaletteDocument(documentValue) { |
186 | | - if (!isObjectRecord(documentValue)) { |
187 | | - return { swatches: null, errors: ["Imported JSON must be an object."] }; |
188 | | - } |
189 | | - |
190 | | -+ if (Array.isArray(documentValue.swatches)) { |
191 | | -+ const swatches = this.cloneImportedSwatches( |
192 | | -+ documentValue.swatches, |
193 | | -+ this.getDirectPaletteSource(documentValue) |
194 | | -+ ); |
195 | | -+ const errors = this.validateUserSwatches(swatches); |
196 | | -+ return { swatches: errors.length > 0 ? null : swatches, errors }; |
197 | | -+ } |
198 | | -+ |
199 | | - if (!isObjectRecord(documentValue.tools)) { |
200 | | -- return { swatches: null, errors: ["Imported JSON must contain a tools object."] }; |
201 | | -+ return { swatches: null, errors: ["Imported JSON must contain a tools object or a direct swatches array."] }; |
202 | | - } |
203 | | - |
204 | | - const paletteValue = documentValue.tools[this.globalPaletteToolKey]; |
205 | | -@@ -70,8 +93,8 @@ export class PaletteValidationService { |
206 | | - return { swatches: null, errors: [`tools.${this.globalPaletteToolKey}.swatches must be an array.`] }; |
207 | | - } |
208 | | - |
209 | | -- const errors = this.validateUserSwatches(paletteValue.swatches); |
210 | | -- const swatches = paletteValue.swatches.map(cloneSwatch); |
211 | | -+ const swatches = this.cloneImportedSwatches(paletteValue.swatches, ""); |
212 | | -+ const errors = this.validateUserSwatches(swatches); |
213 | | - return { swatches: errors.length > 0 ? null : swatches, errors }; |
214 | | - } |
| 65 | +-/* |
| 66 | +-.palette-manager-v2__right-accordion--import .accordion-v2__content { |
| 67 | +- flex: 1 1 auto; |
| 68 | +- min-height: 0; |
| 69 | +- overflow: hidden; |
| 70 | +-} |
| 71 | +-*/ |
| 72 | +- |
| 73 | + .palette-manager-v2__right-accordion--import .palette-manager-v2__json-preview { |
| 74 | + flex: 1 1 auto; |
| 75 | + min-height: 0; |
| 76 | +@@ -325,13 +309,6 @@ body.tools-platform-surface :is(input, select, textarea):hover { |
| 77 | + flex: 0 0 auto; |
215 | 78 | } |
| 79 | + |
| 80 | +-/* |
| 81 | +-.palette-manager-v2__panel--center .accordion-v2__content .palette-manager-v2__tile-grid { |
| 82 | +- flex: 1 1 auto; |
| 83 | +- max-height: none; |
| 84 | +-} |
| 85 | +-*/ |
| 86 | +- |
| 87 | + .palette-manager-v2__field { |
| 88 | + min-width: 0; |
| 89 | + display: grid; |
0 commit comments