From f321e36fd354b9204da06e88fa5b231aaff226ae Mon Sep 17 00:00:00 2001 From: "Null;Variant" Date: Fri, 10 Apr 2026 16:34:57 +0900 Subject: [PATCH 1/2] refactor(git-id-switcher): add document-body design tokens and rename --gis-pad-* to --gis-size-* MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add six new design tokens to :root (--gis-width-readable, --gis-line-height-doc, --gis-border-emphasis, --gis-pad-code, --gis-spinner-size, --gis-spinner-border) and rename --gis-pad-btn/body/body-lg to --gis-size-btn/body/body-lg to reflect their mixed padding/margin usage. Replace all remaining magic numbers in document/loading/error templates with token references. Extend tokenSpec value contracts and add SSOT literal check for --gis-border-emphasis. Signed-off-by: Null;Variant 🖥️ IDE: [VS Code](https://code.visualstudio.com/) 🔌 Extension: [Claude Code](https://claude.ai/download) Co-authored-by: Claude Opus 4.6 Model-Raw: claude-opus-4-6 --- .../src/test/htmlTemplates.test.ts | 24 ++++++++++++++++--- .../src/ui/htmlTemplates/baseStyles.ts | 21 ++++++++++++---- .../src/ui/htmlTemplates/document.ts | 16 ++++++------- .../src/ui/htmlTemplates/error.ts | 2 +- .../src/ui/htmlTemplates/loading.ts | 6 ++--- 5 files changed, 49 insertions(+), 20 deletions(-) diff --git a/extensions/git-id-switcher/src/test/htmlTemplates.test.ts b/extensions/git-id-switcher/src/test/htmlTemplates.test.ts index e615c0b7..3f0911d7 100644 --- a/extensions/git-id-switcher/src/test/htmlTemplates.test.ts +++ b/extensions/git-id-switcher/src/test/htmlTemplates.test.ts @@ -615,11 +615,17 @@ function testDesignTokenCoverage(): void { ['--gis-space-md', /--gis-space-md:\s*1em\b/], ['--gis-space-lg', /--gis-space-lg:\s*1\.5em\b/], ['--gis-space-xl', /--gis-space-xl:\s*2em\b/], - ['--gis-pad-btn', /--gis-pad-btn:\s*4px 12px\b/], - ['--gis-pad-body', /--gis-pad-body:\s*20px\b/], - ['--gis-pad-body-lg', /--gis-pad-body-lg:\s*40px\b/], + ['--gis-size-btn', /--gis-size-btn:\s*4px 12px\b/], + ['--gis-size-body', /--gis-size-body:\s*20px\b/], + ['--gis-size-body-lg', /--gis-size-body-lg:\s*40px\b/], ['--gis-font-sm', /--gis-font-sm:\s*0\.9em\b/], ['--gis-font-xs', /--gis-font-xs:\s*0\.8em\b/], + ['--gis-width-readable', /--gis-width-readable:\s*800px\b/], + ['--gis-line-height-doc', /--gis-line-height-doc:\s*1\.6\b/], + ['--gis-border-emphasis', /--gis-border-emphasis:\s*4px solid var\(--vscode-textLink-foreground\)/], + ['--gis-pad-code', /--gis-pad-code:\s*0\.2em 0\.4em\b/], + ['--gis-spinner-size', /--gis-spinner-size:\s*40px\b/], + ['--gis-spinner-border', /--gis-spinner-border:\s*3px\b/], ]; for (const [name, re] of tokenSpec) { assert.match(styles, re, `${name} value contract violated`); @@ -645,6 +651,18 @@ function testDesignTokenCoverage(): void { `${name}: literal "1px solid var(--vscode-panel-border)" must appear exactly once (in token definition), found ${matches.length}` ); } + // The literal `4px solid var(--vscode-textLink-foreground)` must appear + // exactly once per template — inside the --gis-border-emphasis token + // definition. Same SSOT enforcement as above. + const emphasisPattern = /4px solid var\(--vscode-textLink-foreground\)/g; + for (const [name, html] of allTemplates) { + const matches = html.match(emphasisPattern) ?? []; + assert.strictEqual( + matches.length, 1, + `${name}: literal "4px solid var(--vscode-textLink-foreground)" must appear exactly once (in token definition), found ${matches.length}` + ); + } + // Only the document template actually consumes --gis-border-subtle // (loading/error have no panel-border rules). Asserted separately to // confirm the token is wired up, not merely defined. diff --git a/extensions/git-id-switcher/src/ui/htmlTemplates/baseStyles.ts b/extensions/git-id-switcher/src/ui/htmlTemplates/baseStyles.ts index 5f3979f8..11e961e2 100644 --- a/extensions/git-id-switcher/src/ui/htmlTemplates/baseStyles.ts +++ b/extensions/git-id-switcher/src/ui/htmlTemplates/baseStyles.ts @@ -44,15 +44,26 @@ export function getBaseStyles(): string { --gis-space-md: 1em; --gis-space-lg: 1.5em; --gis-space-xl: 2em; - /* px-based layout spacing (body padding, footer gap, button padding) that - must not scale with inherited font-size. */ - --gis-pad-btn: 4px 12px; - --gis-pad-body: 20px; - --gis-pad-body-lg: 40px; + /* px-based layout sizing (body padding, footer gap, button padding) + that must not scale with inherited font-size. */ + --gis-size-btn: 4px 12px; + --gis-size-body: 20px; + --gis-size-body-lg: 40px; /* font-size tokens. Declared against the document root so nested elements do not multiply em values (0.9em inside a 0.9em ancestor shrinks). */ --gis-font-sm: 0.9em; --gis-font-xs: 0.8em; + /* Document body readability tokens. */ + --gis-width-readable: 800px; + --gis-line-height-doc: 1.6; + /* Emphasis border (blockquote left-edge, distinct from the subtle 1px + panel border). */ + --gis-border-emphasis: 4px solid var(--vscode-textLink-foreground); + /* Inline code padding scale. */ + --gis-pad-code: 0.2em 0.4em; + /* Spinner dimensions. */ + --gis-spinner-size: 40px; + --gis-spinner-border: 3px; } body { font-family: var(--vscode-font-family); diff --git a/extensions/git-id-switcher/src/ui/htmlTemplates/document.ts b/extensions/git-id-switcher/src/ui/htmlTemplates/document.ts index f91fecb1..6345a45f 100644 --- a/extensions/git-id-switcher/src/ui/htmlTemplates/document.ts +++ b/extensions/git-id-switcher/src/ui/htmlTemplates/document.ts @@ -68,7 +68,7 @@ export function buildDocumentHtml( h1 { border-bottom: var(--gis-border-subtle); padding-bottom: var(--gis-space-xs); } code { background-color: var(--vscode-textCodeBlock-background); - padding: 0.2em 0.4em; + padding: var(--gis-pad-code); border-radius: var(--gis-radius-sm); font-family: var(--vscode-editor-font-family); } @@ -111,7 +111,7 @@ export function buildDocumentHtml( background-color: var(--vscode-textCodeBlock-background); } blockquote { - border-left: 4px solid var(--vscode-textLink-foreground); + border-left: var(--gis-border-emphasis); margin: var(--gis-space-md) 0; padding: var(--gis-space-sm) var(--gis-space-md); background-color: var(--vscode-textCodeBlock-background); @@ -131,7 +131,7 @@ export function buildDocumentHtml( background: var(--vscode-button-secondaryBackground); color: var(--vscode-button-secondaryForeground); border: none; - padding: var(--gis-pad-btn); + padding: var(--gis-size-btn); border-radius: var(--gis-radius-sm); cursor: pointer; font-family: var(--vscode-font-family); @@ -169,8 +169,8 @@ ${getFocusVisibleRule('a:focus-visible, button:focus-visible')} ${getFocusVisibleForcedColorsRule('a:focus-visible, button:focus-visible')} } .footer { - margin-top: var(--gis-pad-body-lg); - padding-top: var(--gis-pad-body); + margin-top: var(--gis-size-body-lg); + padding-top: var(--gis-size-body); border-top: var(--gis-border-subtle); font-size: var(--gis-font-sm); } @@ -181,9 +181,9 @@ ${getFocusVisibleForcedColorsRule('a:focus-visible, button:focus-visible')} raise specificity above the base body rule, making cascade order irrelevant. */ body.gis-doc { - padding: var(--gis-pad-body); - line-height: 1.6; - max-width: 800px; + padding: var(--gis-size-body); + line-height: var(--gis-line-height-doc); + max-width: var(--gis-width-readable); margin: 0 auto; }`; diff --git a/extensions/git-id-switcher/src/ui/htmlTemplates/error.ts b/extensions/git-id-switcher/src/ui/htmlTemplates/error.ts index 782dea58..dcef456c 100644 --- a/extensions/git-id-switcher/src/ui/htmlTemplates/error.ts +++ b/extensions/git-id-switcher/src/ui/htmlTemplates/error.ts @@ -67,7 +67,7 @@ export function buildErrorHtml( const extraStyles = ` /* Template-specific body layout — scoped by body.gis-error class to raise specificity above the base body rule. */ body.gis-error { - padding: var(--gis-pad-body-lg); + padding: var(--gis-size-body-lg); text-align: center; } h1 { diff --git a/extensions/git-id-switcher/src/ui/htmlTemplates/loading.ts b/extensions/git-id-switcher/src/ui/htmlTemplates/loading.ts index 7c55ecbb..4eea7812 100644 --- a/extensions/git-id-switcher/src/ui/htmlTemplates/loading.ts +++ b/extensions/git-id-switcher/src/ui/htmlTemplates/loading.ts @@ -28,9 +28,9 @@ export function buildLoadingHtml(cspSource: string, nonce: string): string { height: 100vh; } .spinner { - width: 40px; - height: 40px; - border: 3px solid var(--vscode-panel-border); + width: var(--gis-spinner-size); + height: var(--gis-spinner-size); + border: var(--gis-spinner-border) solid var(--vscode-panel-border); border-top-color: var(--vscode-textLink-foreground); border-radius: 50%; /* circle, not a token */ animation: spin 1s linear infinite; From a704985ee5ef983383b2a1262b6e75797b6841de Mon Sep 17 00:00:00 2001 From: "Null;Variant" Date: Fri, 10 Apr 2026 16:38:57 +0900 Subject: [PATCH 2/2] chore(git-id-switcher): bump version to 0.19.0 and update CHANGELOG MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Null;Variant 🖥️ IDE: [VS Code](https://code.visualstudio.com/) 🔌 Extension: [Claude Code](https://claude.ai/download) Co-authored-by: Claude Opus 4.6 Model-Raw: claude-opus-4-6 --- extensions/git-id-switcher/CHANGELOG.md | 45 +++++++++++++++++++ extensions/git-id-switcher/package.json | 2 +- .../src/ui/documentationInternal.ts | 2 +- 3 files changed, 47 insertions(+), 2 deletions(-) diff --git a/extensions/git-id-switcher/CHANGELOG.md b/extensions/git-id-switcher/CHANGELOG.md index fd95d28a..36c44d8e 100644 --- a/extensions/git-id-switcher/CHANGELOG.md +++ b/extensions/git-id-switcher/CHANGELOG.md @@ -7,6 +7,51 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [0.19.0] - 2026-04-10 + +### Security + +- **Webview CSP hardening**: Tightened `img-src` from wildcard to explicit `assets.nullvariant.com` subdomain (#462) +- **Link scheme allowlist**: Externalized `linkInterceptScript` and restricted navigable href schemes to `http:`, `https:`, and fragment-only (#460) +- **SanitizedHtml branded type**: Introduced a compile-time brand on pre-sanitized HTML content so `buildDocumentHtml` cannot accept a raw `string` (#455) +- **Webview fail-safe fallback**: Hardened CSP construction with `CspValidationError` and a fail-closed error page when CSP assembly fails (#453) +- **Input length guard**: Added input length limit and field name validation to `toFieldError` (#444) + +### Added + +- **Defense-in-depth nonce/lang validation**: `buildHtmlShell` now validates nonce format and lang tag at the template boundary, throwing `CspValidationError` on invalid input (#457) +- **npm namespace placeholder**: Published `@nullvariant/git-id-switcher` placeholder package to prevent name squatting (#422) +- **DCO enforcement**: Added Developer Certificate of Origin check workflow for all PRs (#425) +- **CI auto-approval**: Justice bot now auto-approves PRs that pass all CI checks (#430) +- **Coverage thresholds**: Enforced c8 statement/branch/function/line thresholds via `.c8rc.json` (#434) +- **Allstar policy**: Added OpenSSF Allstar security policy configuration (#413) +- **Snyk integration**: Added Snyk policy, vulnerability badge, and documented in SECURITY.md (#416) +- **FOSSA integration**: Added FOSSA license/security badges and documented in SECURITY.md (#424, #426) + +### Fixed + +- **Webview a11y**: Improved landmark structure, focus management, ARIA attributes, and forced-colors support in all webview templates (#446, #456) +- **Markdown tooltip escaping**: Escape Markdown special characters in status bar tooltip user values (#406) + +### Refactored + +- **htmlTemplates directory split**: Decomposed monolithic `htmlTemplates.ts` into `shell.ts`, `document.ts`, `loading.ts`, `error.ts`, `baseStyles.ts`, `csp.ts`, `linkIntercept.ts`, and `types.ts` (#447, #458, #459) +- **Design tokens round two**: Added six new tokens (`--gis-width-readable`, `--gis-line-height-doc`, `--gis-border-emphasis`, `--gis-pad-code`, `--gis-spinner-size`, `--gis-spinner-border`), renamed `--gis-pad-*` → `--gis-size-*`, and eliminated all remaining magic numbers from templates +- **Design tokens round one**: Scoped webview body overrides by `body.gis-*` class and introduced initial `--gis-border-subtle` / `--gis-space-*` / `--gis-pad-*` token set (#448) +- **Webview template extraction**: Extracted pure HTML template functions from webview provider for independent testability (#405) +- **identityManager split**: Split `identityManager.ts` (1181 → 4 modules) into `identityAddForm.ts`, `identityEditFlow.ts`, `identityFormUtils.ts`, `identityFormValidation.ts` (#401) +- **Validation types module**: Split Phase 4 Unified Validation Types into dedicated `validation-types.ts` (#403) +- **Logging consolidation**: Replaced all `console.log/error/warn/debug` with `OutputChannel`-based `extensionLogger`, added disposed guard (#431, #438, #441) +- **secureLogPath extraction**: Separated log path validation into dedicated `secureLogPath.ts` (#432) +- **AddFormState derivation**: Derived `AddFormState` from `Identity` type and removed `GenericQuickPick` wrapper (#412) +- **Narrowed ESLint exception**: Restricted htmlTemplates `var` allowance to `csp.ts` only (#461) + +### Tests + +- **Branch coverage expansion**: Added tests for `securityLogger` and `configChangeDetector` branch paths (#411) +- **Cross-OS path sanitization**: Added `getSafeStack` tests for Windows/macOS/Linux path formats (#404) +- **Assertion quality**: Improved assertion specificity in `errors.test.ts` and `identityManager.test.ts` (#408, #442) + ## [0.18.0] - 2026-03-30 ### Changed diff --git a/extensions/git-id-switcher/package.json b/extensions/git-id-switcher/package.json index bbf8f4ce..9d11cfec 100644 --- a/extensions/git-id-switcher/package.json +++ b/extensions/git-id-switcher/package.json @@ -2,7 +2,7 @@ "name": "git-id-switcher", "displayName": "%extension.displayName%", "description": "%extension.description%", - "version": "0.18.0", + "version": "0.19.0", "publisher": "nullvariant", "icon": "images/icon.png", "engines": { diff --git a/extensions/git-id-switcher/src/ui/documentationInternal.ts b/extensions/git-id-switcher/src/ui/documentationInternal.ts index 0f5a2697..bdaa46ca 100644 --- a/extensions/git-id-switcher/src/ui/documentationInternal.ts +++ b/extensions/git-id-switcher/src/ui/documentationInternal.ts @@ -33,7 +33,7 @@ export const DOCUMENT_HASHES: Record = { 'AGENTS.md': 'c4918e12fd7900bfc41e708992ebc4b7326600ce9327e8020a986fe4dd807f8d', 'CODE_OF_CONDUCT.md': 'a0e9cb2e004663cdedef4e1adc0e429417ccfc479e367cbc17b869f62ae759d2', 'CONTRIBUTING.md': 'ed4d1f391ffe04e3031dfc9f16fd8fd5dcd54ba23af3b3202c07adac5ba23da7', - 'extensions/git-id-switcher/CHANGELOG.md': 'b11d9b619f23b9e55c31302b9a55f455ade9c58f65ce485b0d6ae4ddeb289e7a', + 'extensions/git-id-switcher/CHANGELOG.md': 'e90ee9f359eb71e410a071afc10b6032ae3cdbbc7e8470a6e189886220d7969d', 'extensions/git-id-switcher/docs/ARCHITECTURE.md': 'd5d879d988054d208739497962a0f937f2f21bdaab51776c4e8363cba99d634c', 'extensions/git-id-switcher/docs/CONTRIBUTING.md': '7d6ad2bc4d8c838790754cb9df848cb65f9fdce7e1a13e5c965b83a3d5b6378c', 'extensions/git-id-switcher/docs/DESIGN_PHILOSOPHY.md': 'f9718b61ac161cb466dbc76845688e7acacf4e5fdc4b8b9553269dba4a094f6b',