2026-05-16 20:48:37 +00:00
|
|
|
|
#!/usr/bin/env python3
|
v3-rewrite [S2]: src/ scaffold + entry.css + tokens layer + bundle_v3.py + token contract verifier
S2 completes the v3 build infrastructure. The cascade layer order is declared in src/entry.css and the tokens layer is populated with v2.30.14-equivalent light and dark global defaults. Verifier confirms the token contract holds (57 light + 72 dark = 129 unique --ogd-* global defaults match dev/* exactly).
Added src/entry.css - declares @layer reset, tokens, base, surfaces, chrome, features, plugins, dark, a11y, hotfix and @imports the tokens modules with explicit layer() annotations. Subsequent steps (S3 onward) uncomment @import lines as their modules ship.
Added src/tokens/00-light-tokens.css - dev/01-tokens.css carried over verbatim; provides :root --ogd-* defaults plus the .theme-light, .theme-dark shared Obsidian-core variable block. 179 lines, untouched values.
Added src/tokens/01-dark-tokens.css - auto-generated by scripts/generate_v3_dark_tokens.py. Contains 72 global dark --ogd-* tokens and 54 core variable overrides across 7 selector groups (.theme-dark, body.theme-dark, body:not(.is-mobile).theme-dark, body:not(.is-mobile).theme-dark.ogd-glass-{reduced,strong,subtle}). Descendant-context dark overrides (105 callout/table/code/heading tokens) stay in dev/ for now; they migrate into surfaces/ and base/ in S3-S5.
Added scripts/generate_v3_dark_tokens.py - selector-aware extractor: walks dev/*.css in _order.txt order, harvests --ogd-* and whitelisted core variable declarations from any non-descendant selector mentioning .theme-dark, groups by selector, writes src/tokens/01-dark-tokens.css. Rerun after changing dev/.
Added scripts/bundle_v3.py - depth-first @import resolver rooted at src/entry.css. Wraps each imported file in @layer name when the @import directive carries layer(name). Generates dist/theme-v3.css (443 lines, !important=0). dist/ stays gitignored; theme.css remains the v2.30.14 build until S11 swap.
Added scripts/verify_v3_token_contract.py - applies the same global-selector rule (:root for light, non-descendant .theme-dark for dark) to both dev/ and src/tokens/, then diffs. Run as the pre-commit guard for any tokens-layer change in v3-rewrite. Passes on the S2 baseline: 57 light + 72 dark defaults match.
validate_theme.py still passes against v2.30.14 (16190 lines / 5816 !important / 37 Style Settings). theme.css unchanged. No regression risk for shipped releases.
2026-05-16 02:04:23 +00:00
|
|
|
|
"""Bundle the v3 src/ tree into dist/theme-v3.css by following the @import
|
|
|
|
|
|
graph rooted at src/entry.css.
|
|
|
|
|
|
|
|
|
|
|
|
Design notes:
|
v3-rewrite [S3.1]: cascade pivot (no @layer) + base modules + v3 fingerprint baseline
Major architectural decision: v3 drops @layer for theme content. CSS Cascade Layers spec says unlayered styles always defeat layered styles, and Obsidian core CSS (app.css) is unlayered. Wrapping the theme in @layer would require !important to win against core, defeating the v3 goal of reducing !important usage. Therefore v3 theme rules stay UNLAYERED and rely on (1) file @import order, (2) selector specificity, (3) remaining !important only where Obsidian core itself uses !important.
Updated docs/v3/design-spec.md: replaced the @layer cascade section with a Cascade Strategy section explaining file-order + specificity. Added a decision log entry for the pivot and a S11 polish note clarifying that !important reduction is deferred from S3 to S11 (strangler pattern: correctness first via fingerprint diff = 0, then audit-driven reduction).
Updated src/entry.css: removed layer() annotations from @import statements; expanded the file header to document the cascade strategy decision. Tokens import remains first, base modules now active, surfaces/chrome/etc. stay commented placeholders for S4+.
Updated scripts/bundle_v3.py: removed @layer wrapping logic. Bundler now emits a plain depth-first concatenation of @import targets. Resulting dist/theme-v3.css is 1223 lines (up from 455 at S2) with !important=332 (mirrors dev/02 + dev/03 sources exactly, will reduce in S11 polish).
Added src/base/10-base-workspace.css - copied verbatim from dev/02-base-workspace.css (118 lines, 14 !important). Provides workspace pane backgrounds, ribbon/tab colors, nav-folder/nav-file title colors, button surfaces, readable-line-width sizing, .markdown-rendered base typography (font-family, font-size from --ogd-body-size token, word-break).
Added src/base/12-reading-content.css - copied verbatim from dev/03-reading-content.css (567 lines, 313 !important). Provides reading view editorial number kicker h1-h4, h2 gradient underline pill, base table structure (border-collapse, th/td padding, first-column accent), blockquote padding strip-back.
Updated dev/v3/golden-rig/obsidian-harness.html: added a URL parameter switcher (?build=v3) so capture scripts can toggle between theme.css (v2 baseline) and dist/theme-v3.css (v3 candidate) without editing the file. Default remains theme.css.
Updated scripts/capture_computed_fingerprint.py: added --build {v2,v3} flag. v3 output goes to docs/v3/computed-fingerprint-v<version>-v3-<theme>.json. Records the build value inside the JSON for traceability.
Added scripts/fp_diff_summary.py - prints a per-element diff count between baseline and candidate fingerprints. Used to track S3-S6 closure progress against the 949 starting diff.
Added scripts/fp_diff_inspect.py - prints every property diff for a single fingerprint element. Used to locate specific computed-style drift when the per-element count is high.
Light fingerprint baseline gap measurement: pre-S3 949 mismatches (v3 was only tokens). After S3.1 base modules activated: 616 mismatches (35 percent closure). Remaining 616 are expected — h1 glassy surface comes from dev/10b + 10d (S5 surfaces / S6 chrome), nav/overlay/popover from S6, callout/table from S5, cm-* from S4 live preview. Captured docs/v3/computed-fingerprint-v2.30.14-v3-{light,dark}.json baselines for the v3 build after S3.1.
validate_theme.py: still passes against v2.30.14 (theme.css unchanged at 16190 lines / 5816 !important / 37 Style Settings).
2026-05-16 02:15:28 +00:00
|
|
|
|
- @import statements are inlined verbatim. v3 deliberately does NOT wrap
|
|
|
|
|
|
imports in @layer because unlayered styles always beat layered ones, and
|
|
|
|
|
|
Obsidian core CSS is unlayered. See src/entry.css for the full rationale.
|
v3-rewrite [S2]: src/ scaffold + entry.css + tokens layer + bundle_v3.py + token contract verifier
S2 completes the v3 build infrastructure. The cascade layer order is declared in src/entry.css and the tokens layer is populated with v2.30.14-equivalent light and dark global defaults. Verifier confirms the token contract holds (57 light + 72 dark = 129 unique --ogd-* global defaults match dev/* exactly).
Added src/entry.css - declares @layer reset, tokens, base, surfaces, chrome, features, plugins, dark, a11y, hotfix and @imports the tokens modules with explicit layer() annotations. Subsequent steps (S3 onward) uncomment @import lines as their modules ship.
Added src/tokens/00-light-tokens.css - dev/01-tokens.css carried over verbatim; provides :root --ogd-* defaults plus the .theme-light, .theme-dark shared Obsidian-core variable block. 179 lines, untouched values.
Added src/tokens/01-dark-tokens.css - auto-generated by scripts/generate_v3_dark_tokens.py. Contains 72 global dark --ogd-* tokens and 54 core variable overrides across 7 selector groups (.theme-dark, body.theme-dark, body:not(.is-mobile).theme-dark, body:not(.is-mobile).theme-dark.ogd-glass-{reduced,strong,subtle}). Descendant-context dark overrides (105 callout/table/code/heading tokens) stay in dev/ for now; they migrate into surfaces/ and base/ in S3-S5.
Added scripts/generate_v3_dark_tokens.py - selector-aware extractor: walks dev/*.css in _order.txt order, harvests --ogd-* and whitelisted core variable declarations from any non-descendant selector mentioning .theme-dark, groups by selector, writes src/tokens/01-dark-tokens.css. Rerun after changing dev/.
Added scripts/bundle_v3.py - depth-first @import resolver rooted at src/entry.css. Wraps each imported file in @layer name when the @import directive carries layer(name). Generates dist/theme-v3.css (443 lines, !important=0). dist/ stays gitignored; theme.css remains the v2.30.14 build until S11 swap.
Added scripts/verify_v3_token_contract.py - applies the same global-selector rule (:root for light, non-descendant .theme-dark for dark) to both dev/ and src/tokens/, then diffs. Run as the pre-commit guard for any tokens-layer change in v3-rewrite. Passes on the S2 baseline: 57 light + 72 dark defaults match.
validate_theme.py still passes against v2.30.14 (16190 lines / 5816 !important / 37 Style Settings). theme.css unchanged. No regression risk for shipped releases.
2026-05-16 02:04:23 +00:00
|
|
|
|
- Re-entrant imports are detected and refused.
|
v3.0.0 release: promote bundle, purge v2 traces, refresh docs
manifest: 2.30.14 -> 3.0.0; theme.css promoted from dist/theme-v3.css (zero declaration-level !important; fingerprint diff Light/Dark = 0). Delete dev/ tree, v2 scripts, v2 docs, v2 fixtures, v2 release artifacts, v2 screenshots. Rewrite scripts/build_release.py and scripts/sync_obsidian_theme.py to v3 toolchain. Inline live_preview_hit_routing_audit and duplicate-selector tokenizer (validate_theme.py and find_safe_duplicate_selectors.py removed). Refresh README.md, CHANGELOG.md, CONTRIBUTING.md, screenshots/README.md, docs/v3/{release-plan,live-preview-editability,golden-image-scenarios,design-spec}.md. Bundle 16509 lines, hit-routing clean, dist/Owen-Graphite-3.0.0.zip (202KB). NOTE: .github/workflows/{validate,release}.yml updates are in a follow-up commit because the current OAuth token lacks 'workflow' scope.
2026-05-16 03:47:50 +00:00
|
|
|
|
- The canonical bundle is dist/theme-v3.css; theme.css at the repo root is a
|
2026-05-16 20:48:37 +00:00
|
|
|
|
copy promoted by dev/scripts/build_release.py or dev/scripts/sync_obsidian_theme.py.
|
v3-rewrite [S2]: src/ scaffold + entry.css + tokens layer + bundle_v3.py + token contract verifier
S2 completes the v3 build infrastructure. The cascade layer order is declared in src/entry.css and the tokens layer is populated with v2.30.14-equivalent light and dark global defaults. Verifier confirms the token contract holds (57 light + 72 dark = 129 unique --ogd-* global defaults match dev/* exactly).
Added src/entry.css - declares @layer reset, tokens, base, surfaces, chrome, features, plugins, dark, a11y, hotfix and @imports the tokens modules with explicit layer() annotations. Subsequent steps (S3 onward) uncomment @import lines as their modules ship.
Added src/tokens/00-light-tokens.css - dev/01-tokens.css carried over verbatim; provides :root --ogd-* defaults plus the .theme-light, .theme-dark shared Obsidian-core variable block. 179 lines, untouched values.
Added src/tokens/01-dark-tokens.css - auto-generated by scripts/generate_v3_dark_tokens.py. Contains 72 global dark --ogd-* tokens and 54 core variable overrides across 7 selector groups (.theme-dark, body.theme-dark, body:not(.is-mobile).theme-dark, body:not(.is-mobile).theme-dark.ogd-glass-{reduced,strong,subtle}). Descendant-context dark overrides (105 callout/table/code/heading tokens) stay in dev/ for now; they migrate into surfaces/ and base/ in S3-S5.
Added scripts/generate_v3_dark_tokens.py - selector-aware extractor: walks dev/*.css in _order.txt order, harvests --ogd-* and whitelisted core variable declarations from any non-descendant selector mentioning .theme-dark, groups by selector, writes src/tokens/01-dark-tokens.css. Rerun after changing dev/.
Added scripts/bundle_v3.py - depth-first @import resolver rooted at src/entry.css. Wraps each imported file in @layer name when the @import directive carries layer(name). Generates dist/theme-v3.css (443 lines, !important=0). dist/ stays gitignored; theme.css remains the v2.30.14 build until S11 swap.
Added scripts/verify_v3_token_contract.py - applies the same global-selector rule (:root for light, non-descendant .theme-dark for dark) to both dev/ and src/tokens/, then diffs. Run as the pre-commit guard for any tokens-layer change in v3-rewrite. Passes on the S2 baseline: 57 light + 72 dark defaults match.
validate_theme.py still passes against v2.30.14 (16190 lines / 5816 !important / 37 Style Settings). theme.css unchanged. No regression risk for shipped releases.
2026-05-16 02:04:23 +00:00
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
|
|
from __future__ import annotations
|
|
|
|
|
|
|
|
|
|
|
|
import argparse
|
|
|
|
|
|
import re
|
|
|
|
|
|
from pathlib import Path
|
|
|
|
|
|
|
2026-05-16 20:48:37 +00:00
|
|
|
|
ROOT = Path(__file__).resolve().parents[2]
|
v3-rewrite [S2]: src/ scaffold + entry.css + tokens layer + bundle_v3.py + token contract verifier
S2 completes the v3 build infrastructure. The cascade layer order is declared in src/entry.css and the tokens layer is populated with v2.30.14-equivalent light and dark global defaults. Verifier confirms the token contract holds (57 light + 72 dark = 129 unique --ogd-* global defaults match dev/* exactly).
Added src/entry.css - declares @layer reset, tokens, base, surfaces, chrome, features, plugins, dark, a11y, hotfix and @imports the tokens modules with explicit layer() annotations. Subsequent steps (S3 onward) uncomment @import lines as their modules ship.
Added src/tokens/00-light-tokens.css - dev/01-tokens.css carried over verbatim; provides :root --ogd-* defaults plus the .theme-light, .theme-dark shared Obsidian-core variable block. 179 lines, untouched values.
Added src/tokens/01-dark-tokens.css - auto-generated by scripts/generate_v3_dark_tokens.py. Contains 72 global dark --ogd-* tokens and 54 core variable overrides across 7 selector groups (.theme-dark, body.theme-dark, body:not(.is-mobile).theme-dark, body:not(.is-mobile).theme-dark.ogd-glass-{reduced,strong,subtle}). Descendant-context dark overrides (105 callout/table/code/heading tokens) stay in dev/ for now; they migrate into surfaces/ and base/ in S3-S5.
Added scripts/generate_v3_dark_tokens.py - selector-aware extractor: walks dev/*.css in _order.txt order, harvests --ogd-* and whitelisted core variable declarations from any non-descendant selector mentioning .theme-dark, groups by selector, writes src/tokens/01-dark-tokens.css. Rerun after changing dev/.
Added scripts/bundle_v3.py - depth-first @import resolver rooted at src/entry.css. Wraps each imported file in @layer name when the @import directive carries layer(name). Generates dist/theme-v3.css (443 lines, !important=0). dist/ stays gitignored; theme.css remains the v2.30.14 build until S11 swap.
Added scripts/verify_v3_token_contract.py - applies the same global-selector rule (:root for light, non-descendant .theme-dark for dark) to both dev/ and src/tokens/, then diffs. Run as the pre-commit guard for any tokens-layer change in v3-rewrite. Passes on the S2 baseline: 57 light + 72 dark defaults match.
validate_theme.py still passes against v2.30.14 (16190 lines / 5816 !important / 37 Style Settings). theme.css unchanged. No regression risk for shipped releases.
2026-05-16 02:04:23 +00:00
|
|
|
|
ENTRY = ROOT / "src" / "entry.css"
|
|
|
|
|
|
DIST = ROOT / "dist" / "theme-v3.css"
|
|
|
|
|
|
|
|
|
|
|
|
IMPORT_RE = re.compile(
|
v3-rewrite [S3.1]: cascade pivot (no @layer) + base modules + v3 fingerprint baseline
Major architectural decision: v3 drops @layer for theme content. CSS Cascade Layers spec says unlayered styles always defeat layered styles, and Obsidian core CSS (app.css) is unlayered. Wrapping the theme in @layer would require !important to win against core, defeating the v3 goal of reducing !important usage. Therefore v3 theme rules stay UNLAYERED and rely on (1) file @import order, (2) selector specificity, (3) remaining !important only where Obsidian core itself uses !important.
Updated docs/v3/design-spec.md: replaced the @layer cascade section with a Cascade Strategy section explaining file-order + specificity. Added a decision log entry for the pivot and a S11 polish note clarifying that !important reduction is deferred from S3 to S11 (strangler pattern: correctness first via fingerprint diff = 0, then audit-driven reduction).
Updated src/entry.css: removed layer() annotations from @import statements; expanded the file header to document the cascade strategy decision. Tokens import remains first, base modules now active, surfaces/chrome/etc. stay commented placeholders for S4+.
Updated scripts/bundle_v3.py: removed @layer wrapping logic. Bundler now emits a plain depth-first concatenation of @import targets. Resulting dist/theme-v3.css is 1223 lines (up from 455 at S2) with !important=332 (mirrors dev/02 + dev/03 sources exactly, will reduce in S11 polish).
Added src/base/10-base-workspace.css - copied verbatim from dev/02-base-workspace.css (118 lines, 14 !important). Provides workspace pane backgrounds, ribbon/tab colors, nav-folder/nav-file title colors, button surfaces, readable-line-width sizing, .markdown-rendered base typography (font-family, font-size from --ogd-body-size token, word-break).
Added src/base/12-reading-content.css - copied verbatim from dev/03-reading-content.css (567 lines, 313 !important). Provides reading view editorial number kicker h1-h4, h2 gradient underline pill, base table structure (border-collapse, th/td padding, first-column accent), blockquote padding strip-back.
Updated dev/v3/golden-rig/obsidian-harness.html: added a URL parameter switcher (?build=v3) so capture scripts can toggle between theme.css (v2 baseline) and dist/theme-v3.css (v3 candidate) without editing the file. Default remains theme.css.
Updated scripts/capture_computed_fingerprint.py: added --build {v2,v3} flag. v3 output goes to docs/v3/computed-fingerprint-v<version>-v3-<theme>.json. Records the build value inside the JSON for traceability.
Added scripts/fp_diff_summary.py - prints a per-element diff count between baseline and candidate fingerprints. Used to track S3-S6 closure progress against the 949 starting diff.
Added scripts/fp_diff_inspect.py - prints every property diff for a single fingerprint element. Used to locate specific computed-style drift when the per-element count is high.
Light fingerprint baseline gap measurement: pre-S3 949 mismatches (v3 was only tokens). After S3.1 base modules activated: 616 mismatches (35 percent closure). Remaining 616 are expected — h1 glassy surface comes from dev/10b + 10d (S5 surfaces / S6 chrome), nav/overlay/popover from S6, callout/table from S5, cm-* from S4 live preview. Captured docs/v3/computed-fingerprint-v2.30.14-v3-{light,dark}.json baselines for the v3 build after S3.1.
validate_theme.py: still passes against v2.30.14 (theme.css unchanged at 16190 lines / 5816 !important / 37 Style Settings).
2026-05-16 02:15:28 +00:00
|
|
|
|
r"""@import\s+url\(\s*['"]?([^'")]+)['"]?\s*\)\s*;""",
|
v3-rewrite [S2]: src/ scaffold + entry.css + tokens layer + bundle_v3.py + token contract verifier
S2 completes the v3 build infrastructure. The cascade layer order is declared in src/entry.css and the tokens layer is populated with v2.30.14-equivalent light and dark global defaults. Verifier confirms the token contract holds (57 light + 72 dark = 129 unique --ogd-* global defaults match dev/* exactly).
Added src/entry.css - declares @layer reset, tokens, base, surfaces, chrome, features, plugins, dark, a11y, hotfix and @imports the tokens modules with explicit layer() annotations. Subsequent steps (S3 onward) uncomment @import lines as their modules ship.
Added src/tokens/00-light-tokens.css - dev/01-tokens.css carried over verbatim; provides :root --ogd-* defaults plus the .theme-light, .theme-dark shared Obsidian-core variable block. 179 lines, untouched values.
Added src/tokens/01-dark-tokens.css - auto-generated by scripts/generate_v3_dark_tokens.py. Contains 72 global dark --ogd-* tokens and 54 core variable overrides across 7 selector groups (.theme-dark, body.theme-dark, body:not(.is-mobile).theme-dark, body:not(.is-mobile).theme-dark.ogd-glass-{reduced,strong,subtle}). Descendant-context dark overrides (105 callout/table/code/heading tokens) stay in dev/ for now; they migrate into surfaces/ and base/ in S3-S5.
Added scripts/generate_v3_dark_tokens.py - selector-aware extractor: walks dev/*.css in _order.txt order, harvests --ogd-* and whitelisted core variable declarations from any non-descendant selector mentioning .theme-dark, groups by selector, writes src/tokens/01-dark-tokens.css. Rerun after changing dev/.
Added scripts/bundle_v3.py - depth-first @import resolver rooted at src/entry.css. Wraps each imported file in @layer name when the @import directive carries layer(name). Generates dist/theme-v3.css (443 lines, !important=0). dist/ stays gitignored; theme.css remains the v2.30.14 build until S11 swap.
Added scripts/verify_v3_token_contract.py - applies the same global-selector rule (:root for light, non-descendant .theme-dark for dark) to both dev/ and src/tokens/, then diffs. Run as the pre-commit guard for any tokens-layer change in v3-rewrite. Passes on the S2 baseline: 57 light + 72 dark defaults match.
validate_theme.py still passes against v2.30.14 (16190 lines / 5816 !important / 37 Style Settings). theme.css unchanged. No regression risk for shipped releases.
2026-05-16 02:04:23 +00:00
|
|
|
|
re.IGNORECASE,
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def is_commented_out(line: str) -> bool:
|
|
|
|
|
|
stripped = line.strip()
|
|
|
|
|
|
return stripped.startswith("/*") or stripped.startswith("//")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def resolve_imports(file_path: Path, visited: set[Path]) -> str:
|
|
|
|
|
|
real = file_path.resolve()
|
|
|
|
|
|
if real in visited:
|
|
|
|
|
|
raise RuntimeError(f"circular @import detected: {real}")
|
|
|
|
|
|
visited = visited | {real}
|
|
|
|
|
|
text = real.read_text(encoding="utf-8")
|
|
|
|
|
|
|
|
|
|
|
|
out_lines: list[str] = []
|
|
|
|
|
|
for raw_line in text.splitlines():
|
|
|
|
|
|
# Only treat a line as an @import directive if the directive is at the
|
v3-rewrite [S3.1]: cascade pivot (no @layer) + base modules + v3 fingerprint baseline
Major architectural decision: v3 drops @layer for theme content. CSS Cascade Layers spec says unlayered styles always defeat layered styles, and Obsidian core CSS (app.css) is unlayered. Wrapping the theme in @layer would require !important to win against core, defeating the v3 goal of reducing !important usage. Therefore v3 theme rules stay UNLAYERED and rely on (1) file @import order, (2) selector specificity, (3) remaining !important only where Obsidian core itself uses !important.
Updated docs/v3/design-spec.md: replaced the @layer cascade section with a Cascade Strategy section explaining file-order + specificity. Added a decision log entry for the pivot and a S11 polish note clarifying that !important reduction is deferred from S3 to S11 (strangler pattern: correctness first via fingerprint diff = 0, then audit-driven reduction).
Updated src/entry.css: removed layer() annotations from @import statements; expanded the file header to document the cascade strategy decision. Tokens import remains first, base modules now active, surfaces/chrome/etc. stay commented placeholders for S4+.
Updated scripts/bundle_v3.py: removed @layer wrapping logic. Bundler now emits a plain depth-first concatenation of @import targets. Resulting dist/theme-v3.css is 1223 lines (up from 455 at S2) with !important=332 (mirrors dev/02 + dev/03 sources exactly, will reduce in S11 polish).
Added src/base/10-base-workspace.css - copied verbatim from dev/02-base-workspace.css (118 lines, 14 !important). Provides workspace pane backgrounds, ribbon/tab colors, nav-folder/nav-file title colors, button surfaces, readable-line-width sizing, .markdown-rendered base typography (font-family, font-size from --ogd-body-size token, word-break).
Added src/base/12-reading-content.css - copied verbatim from dev/03-reading-content.css (567 lines, 313 !important). Provides reading view editorial number kicker h1-h4, h2 gradient underline pill, base table structure (border-collapse, th/td padding, first-column accent), blockquote padding strip-back.
Updated dev/v3/golden-rig/obsidian-harness.html: added a URL parameter switcher (?build=v3) so capture scripts can toggle between theme.css (v2 baseline) and dist/theme-v3.css (v3 candidate) without editing the file. Default remains theme.css.
Updated scripts/capture_computed_fingerprint.py: added --build {v2,v3} flag. v3 output goes to docs/v3/computed-fingerprint-v<version>-v3-<theme>.json. Records the build value inside the JSON for traceability.
Added scripts/fp_diff_summary.py - prints a per-element diff count between baseline and candidate fingerprints. Used to track S3-S6 closure progress against the 949 starting diff.
Added scripts/fp_diff_inspect.py - prints every property diff for a single fingerprint element. Used to locate specific computed-style drift when the per-element count is high.
Light fingerprint baseline gap measurement: pre-S3 949 mismatches (v3 was only tokens). After S3.1 base modules activated: 616 mismatches (35 percent closure). Remaining 616 are expected — h1 glassy surface comes from dev/10b + 10d (S5 surfaces / S6 chrome), nav/overlay/popover from S6, callout/table from S5, cm-* from S4 live preview. Captured docs/v3/computed-fingerprint-v2.30.14-v3-{light,dark}.json baselines for the v3 build after S3.1.
validate_theme.py: still passes against v2.30.14 (theme.css unchanged at 16190 lines / 5816 !important / 37 Style Settings).
2026-05-16 02:15:28 +00:00
|
|
|
|
# start of the line (ignoring whitespace) AND the line is not inside a
|
|
|
|
|
|
# comment. Commented-out @import lines (placeholders for future steps)
|
|
|
|
|
|
# must be passed through verbatim.
|
v3-rewrite [S2]: src/ scaffold + entry.css + tokens layer + bundle_v3.py + token contract verifier
S2 completes the v3 build infrastructure. The cascade layer order is declared in src/entry.css and the tokens layer is populated with v2.30.14-equivalent light and dark global defaults. Verifier confirms the token contract holds (57 light + 72 dark = 129 unique --ogd-* global defaults match dev/* exactly).
Added src/entry.css - declares @layer reset, tokens, base, surfaces, chrome, features, plugins, dark, a11y, hotfix and @imports the tokens modules with explicit layer() annotations. Subsequent steps (S3 onward) uncomment @import lines as their modules ship.
Added src/tokens/00-light-tokens.css - dev/01-tokens.css carried over verbatim; provides :root --ogd-* defaults plus the .theme-light, .theme-dark shared Obsidian-core variable block. 179 lines, untouched values.
Added src/tokens/01-dark-tokens.css - auto-generated by scripts/generate_v3_dark_tokens.py. Contains 72 global dark --ogd-* tokens and 54 core variable overrides across 7 selector groups (.theme-dark, body.theme-dark, body:not(.is-mobile).theme-dark, body:not(.is-mobile).theme-dark.ogd-glass-{reduced,strong,subtle}). Descendant-context dark overrides (105 callout/table/code/heading tokens) stay in dev/ for now; they migrate into surfaces/ and base/ in S3-S5.
Added scripts/generate_v3_dark_tokens.py - selector-aware extractor: walks dev/*.css in _order.txt order, harvests --ogd-* and whitelisted core variable declarations from any non-descendant selector mentioning .theme-dark, groups by selector, writes src/tokens/01-dark-tokens.css. Rerun after changing dev/.
Added scripts/bundle_v3.py - depth-first @import resolver rooted at src/entry.css. Wraps each imported file in @layer name when the @import directive carries layer(name). Generates dist/theme-v3.css (443 lines, !important=0). dist/ stays gitignored; theme.css remains the v2.30.14 build until S11 swap.
Added scripts/verify_v3_token_contract.py - applies the same global-selector rule (:root for light, non-descendant .theme-dark for dark) to both dev/ and src/tokens/, then diffs. Run as the pre-commit guard for any tokens-layer change in v3-rewrite. Passes on the S2 baseline: 57 light + 72 dark defaults match.
validate_theme.py still passes against v2.30.14 (16190 lines / 5816 !important / 37 Style Settings). theme.css unchanged. No regression risk for shipped releases.
2026-05-16 02:04:23 +00:00
|
|
|
|
if "@import" not in raw_line:
|
|
|
|
|
|
out_lines.append(raw_line)
|
|
|
|
|
|
continue
|
|
|
|
|
|
if is_commented_out(raw_line):
|
|
|
|
|
|
out_lines.append(raw_line)
|
|
|
|
|
|
continue
|
|
|
|
|
|
m = IMPORT_RE.search(raw_line)
|
|
|
|
|
|
if not m:
|
|
|
|
|
|
out_lines.append(raw_line)
|
|
|
|
|
|
continue
|
|
|
|
|
|
rel = m.group(1)
|
|
|
|
|
|
target = (real.parent / rel).resolve()
|
|
|
|
|
|
if not target.is_file():
|
|
|
|
|
|
raise FileNotFoundError(f"@import target not found: {rel} (from {real.relative_to(ROOT)})")
|
|
|
|
|
|
inner = resolve_imports(target, visited)
|
|
|
|
|
|
rel_display = target.relative_to(ROOT).as_posix()
|
v3-rewrite [S3.1]: cascade pivot (no @layer) + base modules + v3 fingerprint baseline
Major architectural decision: v3 drops @layer for theme content. CSS Cascade Layers spec says unlayered styles always defeat layered styles, and Obsidian core CSS (app.css) is unlayered. Wrapping the theme in @layer would require !important to win against core, defeating the v3 goal of reducing !important usage. Therefore v3 theme rules stay UNLAYERED and rely on (1) file @import order, (2) selector specificity, (3) remaining !important only where Obsidian core itself uses !important.
Updated docs/v3/design-spec.md: replaced the @layer cascade section with a Cascade Strategy section explaining file-order + specificity. Added a decision log entry for the pivot and a S11 polish note clarifying that !important reduction is deferred from S3 to S11 (strangler pattern: correctness first via fingerprint diff = 0, then audit-driven reduction).
Updated src/entry.css: removed layer() annotations from @import statements; expanded the file header to document the cascade strategy decision. Tokens import remains first, base modules now active, surfaces/chrome/etc. stay commented placeholders for S4+.
Updated scripts/bundle_v3.py: removed @layer wrapping logic. Bundler now emits a plain depth-first concatenation of @import targets. Resulting dist/theme-v3.css is 1223 lines (up from 455 at S2) with !important=332 (mirrors dev/02 + dev/03 sources exactly, will reduce in S11 polish).
Added src/base/10-base-workspace.css - copied verbatim from dev/02-base-workspace.css (118 lines, 14 !important). Provides workspace pane backgrounds, ribbon/tab colors, nav-folder/nav-file title colors, button surfaces, readable-line-width sizing, .markdown-rendered base typography (font-family, font-size from --ogd-body-size token, word-break).
Added src/base/12-reading-content.css - copied verbatim from dev/03-reading-content.css (567 lines, 313 !important). Provides reading view editorial number kicker h1-h4, h2 gradient underline pill, base table structure (border-collapse, th/td padding, first-column accent), blockquote padding strip-back.
Updated dev/v3/golden-rig/obsidian-harness.html: added a URL parameter switcher (?build=v3) so capture scripts can toggle between theme.css (v2 baseline) and dist/theme-v3.css (v3 candidate) without editing the file. Default remains theme.css.
Updated scripts/capture_computed_fingerprint.py: added --build {v2,v3} flag. v3 output goes to docs/v3/computed-fingerprint-v<version>-v3-<theme>.json. Records the build value inside the JSON for traceability.
Added scripts/fp_diff_summary.py - prints a per-element diff count between baseline and candidate fingerprints. Used to track S3-S6 closure progress against the 949 starting diff.
Added scripts/fp_diff_inspect.py - prints every property diff for a single fingerprint element. Used to locate specific computed-style drift when the per-element count is high.
Light fingerprint baseline gap measurement: pre-S3 949 mismatches (v3 was only tokens). After S3.1 base modules activated: 616 mismatches (35 percent closure). Remaining 616 are expected — h1 glassy surface comes from dev/10b + 10d (S5 surfaces / S6 chrome), nav/overlay/popover from S6, callout/table from S5, cm-* from S4 live preview. Captured docs/v3/computed-fingerprint-v2.30.14-v3-{light,dark}.json baselines for the v3 build after S3.1.
validate_theme.py: still passes against v2.30.14 (theme.css unchanged at 16190 lines / 5816 !important / 37 Style Settings).
2026-05-16 02:15:28 +00:00
|
|
|
|
out_lines.append(f"/* >>> {rel_display} */")
|
|
|
|
|
|
out_lines.append(inner)
|
v3-rewrite [S2]: src/ scaffold + entry.css + tokens layer + bundle_v3.py + token contract verifier
S2 completes the v3 build infrastructure. The cascade layer order is declared in src/entry.css and the tokens layer is populated with v2.30.14-equivalent light and dark global defaults. Verifier confirms the token contract holds (57 light + 72 dark = 129 unique --ogd-* global defaults match dev/* exactly).
Added src/entry.css - declares @layer reset, tokens, base, surfaces, chrome, features, plugins, dark, a11y, hotfix and @imports the tokens modules with explicit layer() annotations. Subsequent steps (S3 onward) uncomment @import lines as their modules ship.
Added src/tokens/00-light-tokens.css - dev/01-tokens.css carried over verbatim; provides :root --ogd-* defaults plus the .theme-light, .theme-dark shared Obsidian-core variable block. 179 lines, untouched values.
Added src/tokens/01-dark-tokens.css - auto-generated by scripts/generate_v3_dark_tokens.py. Contains 72 global dark --ogd-* tokens and 54 core variable overrides across 7 selector groups (.theme-dark, body.theme-dark, body:not(.is-mobile).theme-dark, body:not(.is-mobile).theme-dark.ogd-glass-{reduced,strong,subtle}). Descendant-context dark overrides (105 callout/table/code/heading tokens) stay in dev/ for now; they migrate into surfaces/ and base/ in S3-S5.
Added scripts/generate_v3_dark_tokens.py - selector-aware extractor: walks dev/*.css in _order.txt order, harvests --ogd-* and whitelisted core variable declarations from any non-descendant selector mentioning .theme-dark, groups by selector, writes src/tokens/01-dark-tokens.css. Rerun after changing dev/.
Added scripts/bundle_v3.py - depth-first @import resolver rooted at src/entry.css. Wraps each imported file in @layer name when the @import directive carries layer(name). Generates dist/theme-v3.css (443 lines, !important=0). dist/ stays gitignored; theme.css remains the v2.30.14 build until S11 swap.
Added scripts/verify_v3_token_contract.py - applies the same global-selector rule (:root for light, non-descendant .theme-dark for dark) to both dev/ and src/tokens/, then diffs. Run as the pre-commit guard for any tokens-layer change in v3-rewrite. Passes on the S2 baseline: 57 light + 72 dark defaults match.
validate_theme.py still passes against v2.30.14 (16190 lines / 5816 !important / 37 Style Settings). theme.css unchanged. No regression risk for shipped releases.
2026-05-16 02:04:23 +00:00
|
|
|
|
out_lines.append(f"/* <<< {rel_display} */")
|
|
|
|
|
|
return "\n".join(out_lines)
|
|
|
|
|
|
|
|
|
|
|
|
|
2026-05-16 05:31:17 +00:00
|
|
|
|
def bundle(entry: Path, dist: Path, dedup: bool = True, check: bool = False) -> int:
|
v3-rewrite [S2]: src/ scaffold + entry.css + tokens layer + bundle_v3.py + token contract verifier
S2 completes the v3 build infrastructure. The cascade layer order is declared in src/entry.css and the tokens layer is populated with v2.30.14-equivalent light and dark global defaults. Verifier confirms the token contract holds (57 light + 72 dark = 129 unique --ogd-* global defaults match dev/* exactly).
Added src/entry.css - declares @layer reset, tokens, base, surfaces, chrome, features, plugins, dark, a11y, hotfix and @imports the tokens modules with explicit layer() annotations. Subsequent steps (S3 onward) uncomment @import lines as their modules ship.
Added src/tokens/00-light-tokens.css - dev/01-tokens.css carried over verbatim; provides :root --ogd-* defaults plus the .theme-light, .theme-dark shared Obsidian-core variable block. 179 lines, untouched values.
Added src/tokens/01-dark-tokens.css - auto-generated by scripts/generate_v3_dark_tokens.py. Contains 72 global dark --ogd-* tokens and 54 core variable overrides across 7 selector groups (.theme-dark, body.theme-dark, body:not(.is-mobile).theme-dark, body:not(.is-mobile).theme-dark.ogd-glass-{reduced,strong,subtle}). Descendant-context dark overrides (105 callout/table/code/heading tokens) stay in dev/ for now; they migrate into surfaces/ and base/ in S3-S5.
Added scripts/generate_v3_dark_tokens.py - selector-aware extractor: walks dev/*.css in _order.txt order, harvests --ogd-* and whitelisted core variable declarations from any non-descendant selector mentioning .theme-dark, groups by selector, writes src/tokens/01-dark-tokens.css. Rerun after changing dev/.
Added scripts/bundle_v3.py - depth-first @import resolver rooted at src/entry.css. Wraps each imported file in @layer name when the @import directive carries layer(name). Generates dist/theme-v3.css (443 lines, !important=0). dist/ stays gitignored; theme.css remains the v2.30.14 build until S11 swap.
Added scripts/verify_v3_token_contract.py - applies the same global-selector rule (:root for light, non-descendant .theme-dark for dark) to both dev/ and src/tokens/, then diffs. Run as the pre-commit guard for any tokens-layer change in v3-rewrite. Passes on the S2 baseline: 57 light + 72 dark defaults match.
validate_theme.py still passes against v2.30.14 (16190 lines / 5816 !important / 37 Style Settings). theme.css unchanged. No regression risk for shipped releases.
2026-05-16 02:04:23 +00:00
|
|
|
|
if not entry.is_file():
|
|
|
|
|
|
print(f"ERROR: entry not found: {entry}")
|
|
|
|
|
|
return 1
|
|
|
|
|
|
bundled = resolve_imports(entry, set())
|
|
|
|
|
|
dist.parent.mkdir(parents=True, exist_ok=True)
|
|
|
|
|
|
header = (
|
2026-05-16 05:36:09 +00:00
|
|
|
|
"/* Owen Graphite v3 — bundled theme stylesheet.\n"
|
v3-rewrite [S2]: src/ scaffold + entry.css + tokens layer + bundle_v3.py + token contract verifier
S2 completes the v3 build infrastructure. The cascade layer order is declared in src/entry.css and the tokens layer is populated with v2.30.14-equivalent light and dark global defaults. Verifier confirms the token contract holds (57 light + 72 dark = 129 unique --ogd-* global defaults match dev/* exactly).
Added src/entry.css - declares @layer reset, tokens, base, surfaces, chrome, features, plugins, dark, a11y, hotfix and @imports the tokens modules with explicit layer() annotations. Subsequent steps (S3 onward) uncomment @import lines as their modules ship.
Added src/tokens/00-light-tokens.css - dev/01-tokens.css carried over verbatim; provides :root --ogd-* defaults plus the .theme-light, .theme-dark shared Obsidian-core variable block. 179 lines, untouched values.
Added src/tokens/01-dark-tokens.css - auto-generated by scripts/generate_v3_dark_tokens.py. Contains 72 global dark --ogd-* tokens and 54 core variable overrides across 7 selector groups (.theme-dark, body.theme-dark, body:not(.is-mobile).theme-dark, body:not(.is-mobile).theme-dark.ogd-glass-{reduced,strong,subtle}). Descendant-context dark overrides (105 callout/table/code/heading tokens) stay in dev/ for now; they migrate into surfaces/ and base/ in S3-S5.
Added scripts/generate_v3_dark_tokens.py - selector-aware extractor: walks dev/*.css in _order.txt order, harvests --ogd-* and whitelisted core variable declarations from any non-descendant selector mentioning .theme-dark, groups by selector, writes src/tokens/01-dark-tokens.css. Rerun after changing dev/.
Added scripts/bundle_v3.py - depth-first @import resolver rooted at src/entry.css. Wraps each imported file in @layer name when the @import directive carries layer(name). Generates dist/theme-v3.css (443 lines, !important=0). dist/ stays gitignored; theme.css remains the v2.30.14 build until S11 swap.
Added scripts/verify_v3_token_contract.py - applies the same global-selector rule (:root for light, non-descendant .theme-dark for dark) to both dev/ and src/tokens/, then diffs. Run as the pre-commit guard for any tokens-layer change in v3-rewrite. Passes on the S2 baseline: 57 light + 72 dark defaults match.
validate_theme.py still passes against v2.30.14 (16190 lines / 5816 !important / 37 Style Settings). theme.css unchanged. No regression risk for shipped releases.
2026-05-16 02:04:23 +00:00
|
|
|
|
" *\n"
|
2026-05-16 20:48:37 +00:00
|
|
|
|
" * Generated by dev/scripts/bundle_v3.py from src/entry.css.\n"
|
2026-05-16 05:36:09 +00:00
|
|
|
|
" * Do not hand-edit. The @import / cascade order is defined in src/entry.css.\n"
|
2026-05-16 20:48:37 +00:00
|
|
|
|
" * Same-context duplicate-selector dedup pass: dev/scripts/dedup_v3.py.\n"
|
v3-rewrite [S2]: src/ scaffold + entry.css + tokens layer + bundle_v3.py + token contract verifier
S2 completes the v3 build infrastructure. The cascade layer order is declared in src/entry.css and the tokens layer is populated with v2.30.14-equivalent light and dark global defaults. Verifier confirms the token contract holds (57 light + 72 dark = 129 unique --ogd-* global defaults match dev/* exactly).
Added src/entry.css - declares @layer reset, tokens, base, surfaces, chrome, features, plugins, dark, a11y, hotfix and @imports the tokens modules with explicit layer() annotations. Subsequent steps (S3 onward) uncomment @import lines as their modules ship.
Added src/tokens/00-light-tokens.css - dev/01-tokens.css carried over verbatim; provides :root --ogd-* defaults plus the .theme-light, .theme-dark shared Obsidian-core variable block. 179 lines, untouched values.
Added src/tokens/01-dark-tokens.css - auto-generated by scripts/generate_v3_dark_tokens.py. Contains 72 global dark --ogd-* tokens and 54 core variable overrides across 7 selector groups (.theme-dark, body.theme-dark, body:not(.is-mobile).theme-dark, body:not(.is-mobile).theme-dark.ogd-glass-{reduced,strong,subtle}). Descendant-context dark overrides (105 callout/table/code/heading tokens) stay in dev/ for now; they migrate into surfaces/ and base/ in S3-S5.
Added scripts/generate_v3_dark_tokens.py - selector-aware extractor: walks dev/*.css in _order.txt order, harvests --ogd-* and whitelisted core variable declarations from any non-descendant selector mentioning .theme-dark, groups by selector, writes src/tokens/01-dark-tokens.css. Rerun after changing dev/.
Added scripts/bundle_v3.py - depth-first @import resolver rooted at src/entry.css. Wraps each imported file in @layer name when the @import directive carries layer(name). Generates dist/theme-v3.css (443 lines, !important=0). dist/ stays gitignored; theme.css remains the v2.30.14 build until S11 swap.
Added scripts/verify_v3_token_contract.py - applies the same global-selector rule (:root for light, non-descendant .theme-dark for dark) to both dev/ and src/tokens/, then diffs. Run as the pre-commit guard for any tokens-layer change in v3-rewrite. Passes on the S2 baseline: 57 light + 72 dark defaults match.
validate_theme.py still passes against v2.30.14 (16190 lines / 5816 !important / 37 Style Settings). theme.css unchanged. No regression risk for shipped releases.
2026-05-16 02:04:23 +00:00
|
|
|
|
" *\n"
|
2026-05-16 05:36:09 +00:00
|
|
|
|
" * The repo root theme.css is a promoted copy of this bundle; see\n"
|
2026-05-16 20:48:37 +00:00
|
|
|
|
" * dev/scripts/build_release.py and dev/scripts/sync_obsidian_theme.py.\n"
|
v3-rewrite [S2]: src/ scaffold + entry.css + tokens layer + bundle_v3.py + token contract verifier
S2 completes the v3 build infrastructure. The cascade layer order is declared in src/entry.css and the tokens layer is populated with v2.30.14-equivalent light and dark global defaults. Verifier confirms the token contract holds (57 light + 72 dark = 129 unique --ogd-* global defaults match dev/* exactly).
Added src/entry.css - declares @layer reset, tokens, base, surfaces, chrome, features, plugins, dark, a11y, hotfix and @imports the tokens modules with explicit layer() annotations. Subsequent steps (S3 onward) uncomment @import lines as their modules ship.
Added src/tokens/00-light-tokens.css - dev/01-tokens.css carried over verbatim; provides :root --ogd-* defaults plus the .theme-light, .theme-dark shared Obsidian-core variable block. 179 lines, untouched values.
Added src/tokens/01-dark-tokens.css - auto-generated by scripts/generate_v3_dark_tokens.py. Contains 72 global dark --ogd-* tokens and 54 core variable overrides across 7 selector groups (.theme-dark, body.theme-dark, body:not(.is-mobile).theme-dark, body:not(.is-mobile).theme-dark.ogd-glass-{reduced,strong,subtle}). Descendant-context dark overrides (105 callout/table/code/heading tokens) stay in dev/ for now; they migrate into surfaces/ and base/ in S3-S5.
Added scripts/generate_v3_dark_tokens.py - selector-aware extractor: walks dev/*.css in _order.txt order, harvests --ogd-* and whitelisted core variable declarations from any non-descendant selector mentioning .theme-dark, groups by selector, writes src/tokens/01-dark-tokens.css. Rerun after changing dev/.
Added scripts/bundle_v3.py - depth-first @import resolver rooted at src/entry.css. Wraps each imported file in @layer name when the @import directive carries layer(name). Generates dist/theme-v3.css (443 lines, !important=0). dist/ stays gitignored; theme.css remains the v2.30.14 build until S11 swap.
Added scripts/verify_v3_token_contract.py - applies the same global-selector rule (:root for light, non-descendant .theme-dark for dark) to both dev/ and src/tokens/, then diffs. Run as the pre-commit guard for any tokens-layer change in v3-rewrite. Passes on the S2 baseline: 57 light + 72 dark defaults match.
validate_theme.py still passes against v2.30.14 (16190 lines / 5816 !important / 37 Style Settings). theme.css unchanged. No regression risk for shipped releases.
2026-05-16 02:04:23 +00:00
|
|
|
|
" */\n\n"
|
|
|
|
|
|
)
|
2026-05-16 05:25:45 +00:00
|
|
|
|
merges = 0
|
|
|
|
|
|
if dedup:
|
|
|
|
|
|
# Import lazily so bundle_v3.py remains importable even if dedup_v3
|
|
|
|
|
|
# is removed/relocated.
|
|
|
|
|
|
import importlib.util
|
2026-05-16 20:48:37 +00:00
|
|
|
|
dedup_path = ROOT / "dev" / "scripts" / "dedup_v3.py"
|
2026-05-16 05:25:45 +00:00
|
|
|
|
spec = importlib.util.spec_from_file_location("dedup_v3", dedup_path)
|
|
|
|
|
|
if spec is None or spec.loader is None:
|
2026-05-16 20:48:37 +00:00
|
|
|
|
raise RuntimeError("unable to load dev/scripts/dedup_v3.py")
|
2026-05-16 05:25:45 +00:00
|
|
|
|
dedup_mod = importlib.util.module_from_spec(spec)
|
|
|
|
|
|
spec.loader.exec_module(dedup_mod)
|
|
|
|
|
|
bundled, merges = dedup_mod.dedup_scope(bundled)
|
2026-05-17 03:26:21 +00:00
|
|
|
|
bundled = "\n".join(line.rstrip() for line in bundled.splitlines())
|
2026-05-16 05:31:17 +00:00
|
|
|
|
new_content = header + bundled + "\n"
|
v3-rewrite [S2]: src/ scaffold + entry.css + tokens layer + bundle_v3.py + token contract verifier
S2 completes the v3 build infrastructure. The cascade layer order is declared in src/entry.css and the tokens layer is populated with v2.30.14-equivalent light and dark global defaults. Verifier confirms the token contract holds (57 light + 72 dark = 129 unique --ogd-* global defaults match dev/* exactly).
Added src/entry.css - declares @layer reset, tokens, base, surfaces, chrome, features, plugins, dark, a11y, hotfix and @imports the tokens modules with explicit layer() annotations. Subsequent steps (S3 onward) uncomment @import lines as their modules ship.
Added src/tokens/00-light-tokens.css - dev/01-tokens.css carried over verbatim; provides :root --ogd-* defaults plus the .theme-light, .theme-dark shared Obsidian-core variable block. 179 lines, untouched values.
Added src/tokens/01-dark-tokens.css - auto-generated by scripts/generate_v3_dark_tokens.py. Contains 72 global dark --ogd-* tokens and 54 core variable overrides across 7 selector groups (.theme-dark, body.theme-dark, body:not(.is-mobile).theme-dark, body:not(.is-mobile).theme-dark.ogd-glass-{reduced,strong,subtle}). Descendant-context dark overrides (105 callout/table/code/heading tokens) stay in dev/ for now; they migrate into surfaces/ and base/ in S3-S5.
Added scripts/generate_v3_dark_tokens.py - selector-aware extractor: walks dev/*.css in _order.txt order, harvests --ogd-* and whitelisted core variable declarations from any non-descendant selector mentioning .theme-dark, groups by selector, writes src/tokens/01-dark-tokens.css. Rerun after changing dev/.
Added scripts/bundle_v3.py - depth-first @import resolver rooted at src/entry.css. Wraps each imported file in @layer name when the @import directive carries layer(name). Generates dist/theme-v3.css (443 lines, !important=0). dist/ stays gitignored; theme.css remains the v2.30.14 build until S11 swap.
Added scripts/verify_v3_token_contract.py - applies the same global-selector rule (:root for light, non-descendant .theme-dark for dark) to both dev/ and src/tokens/, then diffs. Run as the pre-commit guard for any tokens-layer change in v3-rewrite. Passes on the S2 baseline: 57 light + 72 dark defaults match.
validate_theme.py still passes against v2.30.14 (16190 lines / 5816 !important / 37 Style Settings). theme.css unchanged. No regression risk for shipped releases.
2026-05-16 02:04:23 +00:00
|
|
|
|
line_count = bundled.count("\n") + 1
|
|
|
|
|
|
bang_count = bundled.count("!important")
|
2026-05-16 05:31:17 +00:00
|
|
|
|
if check:
|
|
|
|
|
|
# Determinism / freshness gate: compare against existing dist file.
|
|
|
|
|
|
if not dist.is_file():
|
|
|
|
|
|
print(f"FAIL: {dist.relative_to(ROOT)} missing; run bundle_v3.py to generate.")
|
|
|
|
|
|
return 1
|
|
|
|
|
|
current = dist.read_text(encoding="utf-8")
|
|
|
|
|
|
if current != new_content:
|
|
|
|
|
|
print(
|
|
|
|
|
|
f"FAIL: {dist.relative_to(ROOT)} is stale vs src/.\n"
|
2026-05-16 20:48:37 +00:00
|
|
|
|
f" Re-run: python dev/scripts/bundle_v3.py"
|
2026-05-16 05:31:17 +00:00
|
|
|
|
)
|
|
|
|
|
|
return 1
|
|
|
|
|
|
print(
|
|
|
|
|
|
f"OK: {dist.relative_to(ROOT)} matches src/ "
|
|
|
|
|
|
f"({line_count} lines, !important={bang_count}, dedup_merges={merges})"
|
|
|
|
|
|
)
|
|
|
|
|
|
return 0
|
|
|
|
|
|
dist.write_text(new_content, encoding="utf-8")
|
2026-05-16 05:25:45 +00:00
|
|
|
|
print(
|
|
|
|
|
|
f"OK: bundled {dist.relative_to(ROOT)} "
|
|
|
|
|
|
f"({line_count} lines, !important={bang_count}, dedup_merges={merges})"
|
|
|
|
|
|
)
|
v3-rewrite [S2]: src/ scaffold + entry.css + tokens layer + bundle_v3.py + token contract verifier
S2 completes the v3 build infrastructure. The cascade layer order is declared in src/entry.css and the tokens layer is populated with v2.30.14-equivalent light and dark global defaults. Verifier confirms the token contract holds (57 light + 72 dark = 129 unique --ogd-* global defaults match dev/* exactly).
Added src/entry.css - declares @layer reset, tokens, base, surfaces, chrome, features, plugins, dark, a11y, hotfix and @imports the tokens modules with explicit layer() annotations. Subsequent steps (S3 onward) uncomment @import lines as their modules ship.
Added src/tokens/00-light-tokens.css - dev/01-tokens.css carried over verbatim; provides :root --ogd-* defaults plus the .theme-light, .theme-dark shared Obsidian-core variable block. 179 lines, untouched values.
Added src/tokens/01-dark-tokens.css - auto-generated by scripts/generate_v3_dark_tokens.py. Contains 72 global dark --ogd-* tokens and 54 core variable overrides across 7 selector groups (.theme-dark, body.theme-dark, body:not(.is-mobile).theme-dark, body:not(.is-mobile).theme-dark.ogd-glass-{reduced,strong,subtle}). Descendant-context dark overrides (105 callout/table/code/heading tokens) stay in dev/ for now; they migrate into surfaces/ and base/ in S3-S5.
Added scripts/generate_v3_dark_tokens.py - selector-aware extractor: walks dev/*.css in _order.txt order, harvests --ogd-* and whitelisted core variable declarations from any non-descendant selector mentioning .theme-dark, groups by selector, writes src/tokens/01-dark-tokens.css. Rerun after changing dev/.
Added scripts/bundle_v3.py - depth-first @import resolver rooted at src/entry.css. Wraps each imported file in @layer name when the @import directive carries layer(name). Generates dist/theme-v3.css (443 lines, !important=0). dist/ stays gitignored; theme.css remains the v2.30.14 build until S11 swap.
Added scripts/verify_v3_token_contract.py - applies the same global-selector rule (:root for light, non-descendant .theme-dark for dark) to both dev/ and src/tokens/, then diffs. Run as the pre-commit guard for any tokens-layer change in v3-rewrite. Passes on the S2 baseline: 57 light + 72 dark defaults match.
validate_theme.py still passes against v2.30.14 (16190 lines / 5816 !important / 37 Style Settings). theme.css unchanged. No regression risk for shipped releases.
2026-05-16 02:04:23 +00:00
|
|
|
|
return 0
|
|
|
|
|
|
|
|
|
|
|
|
|
2026-05-25 06:22:04 +00:00
|
|
|
|
def main(argv: list[str] | None = None) -> int:
|
v3-rewrite [S2]: src/ scaffold + entry.css + tokens layer + bundle_v3.py + token contract verifier
S2 completes the v3 build infrastructure. The cascade layer order is declared in src/entry.css and the tokens layer is populated with v2.30.14-equivalent light and dark global defaults. Verifier confirms the token contract holds (57 light + 72 dark = 129 unique --ogd-* global defaults match dev/* exactly).
Added src/entry.css - declares @layer reset, tokens, base, surfaces, chrome, features, plugins, dark, a11y, hotfix and @imports the tokens modules with explicit layer() annotations. Subsequent steps (S3 onward) uncomment @import lines as their modules ship.
Added src/tokens/00-light-tokens.css - dev/01-tokens.css carried over verbatim; provides :root --ogd-* defaults plus the .theme-light, .theme-dark shared Obsidian-core variable block. 179 lines, untouched values.
Added src/tokens/01-dark-tokens.css - auto-generated by scripts/generate_v3_dark_tokens.py. Contains 72 global dark --ogd-* tokens and 54 core variable overrides across 7 selector groups (.theme-dark, body.theme-dark, body:not(.is-mobile).theme-dark, body:not(.is-mobile).theme-dark.ogd-glass-{reduced,strong,subtle}). Descendant-context dark overrides (105 callout/table/code/heading tokens) stay in dev/ for now; they migrate into surfaces/ and base/ in S3-S5.
Added scripts/generate_v3_dark_tokens.py - selector-aware extractor: walks dev/*.css in _order.txt order, harvests --ogd-* and whitelisted core variable declarations from any non-descendant selector mentioning .theme-dark, groups by selector, writes src/tokens/01-dark-tokens.css. Rerun after changing dev/.
Added scripts/bundle_v3.py - depth-first @import resolver rooted at src/entry.css. Wraps each imported file in @layer name when the @import directive carries layer(name). Generates dist/theme-v3.css (443 lines, !important=0). dist/ stays gitignored; theme.css remains the v2.30.14 build until S11 swap.
Added scripts/verify_v3_token_contract.py - applies the same global-selector rule (:root for light, non-descendant .theme-dark for dark) to both dev/ and src/tokens/, then diffs. Run as the pre-commit guard for any tokens-layer change in v3-rewrite. Passes on the S2 baseline: 57 light + 72 dark defaults match.
validate_theme.py still passes against v2.30.14 (16190 lines / 5816 !important / 37 Style Settings). theme.css unchanged. No regression risk for shipped releases.
2026-05-16 02:04:23 +00:00
|
|
|
|
parser = argparse.ArgumentParser(description=__doc__)
|
|
|
|
|
|
parser.add_argument("--entry", type=Path, default=ENTRY)
|
|
|
|
|
|
parser.add_argument("--out", type=Path, default=DIST)
|
2026-05-16 05:25:45 +00:00
|
|
|
|
parser.add_argument("--no-dedup", action="store_true",
|
|
|
|
|
|
help="Skip the post-bundle same-context selector dedup pass.")
|
2026-05-16 05:31:17 +00:00
|
|
|
|
parser.add_argument("--check", action="store_true",
|
|
|
|
|
|
help="Verify that dist matches a fresh bundle from src/; do not write.")
|
2026-05-25 06:22:04 +00:00
|
|
|
|
args = parser.parse_args(argv)
|
2026-05-16 05:31:17 +00:00
|
|
|
|
return bundle(args.entry, args.out, dedup=not args.no_dedup, check=args.check)
|
v3-rewrite [S2]: src/ scaffold + entry.css + tokens layer + bundle_v3.py + token contract verifier
S2 completes the v3 build infrastructure. The cascade layer order is declared in src/entry.css and the tokens layer is populated with v2.30.14-equivalent light and dark global defaults. Verifier confirms the token contract holds (57 light + 72 dark = 129 unique --ogd-* global defaults match dev/* exactly).
Added src/entry.css - declares @layer reset, tokens, base, surfaces, chrome, features, plugins, dark, a11y, hotfix and @imports the tokens modules with explicit layer() annotations. Subsequent steps (S3 onward) uncomment @import lines as their modules ship.
Added src/tokens/00-light-tokens.css - dev/01-tokens.css carried over verbatim; provides :root --ogd-* defaults plus the .theme-light, .theme-dark shared Obsidian-core variable block. 179 lines, untouched values.
Added src/tokens/01-dark-tokens.css - auto-generated by scripts/generate_v3_dark_tokens.py. Contains 72 global dark --ogd-* tokens and 54 core variable overrides across 7 selector groups (.theme-dark, body.theme-dark, body:not(.is-mobile).theme-dark, body:not(.is-mobile).theme-dark.ogd-glass-{reduced,strong,subtle}). Descendant-context dark overrides (105 callout/table/code/heading tokens) stay in dev/ for now; they migrate into surfaces/ and base/ in S3-S5.
Added scripts/generate_v3_dark_tokens.py - selector-aware extractor: walks dev/*.css in _order.txt order, harvests --ogd-* and whitelisted core variable declarations from any non-descendant selector mentioning .theme-dark, groups by selector, writes src/tokens/01-dark-tokens.css. Rerun after changing dev/.
Added scripts/bundle_v3.py - depth-first @import resolver rooted at src/entry.css. Wraps each imported file in @layer name when the @import directive carries layer(name). Generates dist/theme-v3.css (443 lines, !important=0). dist/ stays gitignored; theme.css remains the v2.30.14 build until S11 swap.
Added scripts/verify_v3_token_contract.py - applies the same global-selector rule (:root for light, non-descendant .theme-dark for dark) to both dev/ and src/tokens/, then diffs. Run as the pre-commit guard for any tokens-layer change in v3-rewrite. Passes on the S2 baseline: 57 light + 72 dark defaults match.
validate_theme.py still passes against v2.30.14 (16190 lines / 5816 !important / 37 Style Settings). theme.css unchanged. No regression risk for shipped releases.
2026-05-16 02:04:23 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if __name__ == "__main__":
|
|
|
|
|
|
raise SystemExit(main())
|