studio: seed 25-tab fashion workspace idempotently (guard v3)

Move the workspace-tab seeder out of a hand-edited index.html and into a
dedicated branding patcher wired into apply-branding.sh, so it survives a
frontend-package reinstall and re-applies on every branding pass.

Root cause the seeder addresses: the frontend restores open tabs from
localStorage via getStorageValue/setStorageValue, whose clientId-suffixed
sessionStorage layer is read BEFORE plain localStorage. A previously-saved
1-tab shadow (Comfy.OpenWorkflowsPaths:<clientId>) masked the seed, and the
old guard was burned at v2 so existing profiles never re-seeded.

Fix: bump guard v2->v3 (existing profiles re-seed once) and purge the stale
sessionStorage shadows on re-seed so the fresh localStorage seed wins even on
a same-tab reload. Clean-profile boot now opens all 25 fashion workflows
(alongside ComfyUI's own default tab).
This commit is contained in:
hanzo-dev
2026-07-03 20:49:21 -07:00
parent 4ab38a1f06
commit 99c3d53d8b
2 changed files with 94 additions and 1 deletions
+8 -1
View File
@@ -71,7 +71,14 @@ python3 "$BRANDING_DIR/patch_sidebar_logo.py" "$STATIC_DIR"
# --- 7. Apply the black + Hanzo-purple theme ---
# Installs hanzo-theme.css, injects it as the last stylesheet in index.html, and
# rewrites the "dark" palette's canvas clear color to pure black.
echo "[7/7] Applying black + purple theme..."
echo "[7/8] Applying black + purple theme..."
python3 "$BRANDING_DIR/patch_theme.py" "$STATIC_DIR" "$BRANDING_DIR"
# --- 8. Seed the default 25-tab fashion workspace ---
# Injects a <head> bootstrap that pre-populates the localStorage keys the
# frontend reads to restore open workflow tabs. Idempotent; bump SEED_VERSION
# in patch_workspace_seed.py to force existing profiles to re-seed once.
echo "[8/8] Seeding default workspace tabs..."
python3 "$BRANDING_DIR/patch_workspace_seed.py" "$STATIC_DIR"
echo "=== Hanzo Studio branding complete ==="
+86
View File
@@ -0,0 +1,86 @@
#!/usr/bin/env python3
"""
Seed the default multi-tab fashion workspace for Hanzo Studio.
Injects a tiny <head> bootstrap script into index.html that pre-populates the
localStorage keys the frontend reads on boot to restore open workflow tabs:
Comfy.OpenWorkflowsPaths JSON array of workflow paths (one tab each)
Comfy.ActiveWorkflowIndex restore split index (0 = open all as trailing tabs)
Why localStorage: the frontend's restore path (restoreWorkflowTabsState in
GraphView useWorkflowPersistence) reads these through getStorageValue()/
setStorageValue(), which are localStorage helpers with a clientId-suffixed
sessionStorage layer read FIRST. On re-seed we therefore also purge any stale
`<key>:<clientId>` sessionStorage shadows so the fresh localStorage seed is
authoritative even on a same-tab reload (otherwise a previously-saved 1-tab
shadow would mask the seed).
ComfyUI always opens its own blank "Unsaved Workflow" as the active tab on boot
(initializeWorkflow -> loadDefaultWorkflow); the 25 seeded fashion workflows are
restored alongside it in the background, so the workspace opens with 26 tabs.
There is no seed key that suppresses the default tab: loadPersistedWorkflow only
rehydrates drafts / the last-edited graph blob, never a userdata file by path.
The seed runs at most once per browser profile, guarded by hanzoWorkspaceSeed.
Bump SEED_VERSION to force every existing profile to re-seed exactly once.
Idempotent: re-running strips the previous seed <script> and re-injects the
current one, so this is safe to call on every branding pass.
"""
import re
import sys
STATIC_DIR = sys.argv[1]
INDEX = f"{STATIC_DIR}/index.html"
SEED_VERSION = "v3"
# Curated 8-design x 3-pose matrix + the shared 4K upscaler = 25 tabs.
DESIGNS = [
"festival_fuchsia", "neptune_green", "floralmesh", "stripedgem",
"redonepiece", "valentina", "red", "ches",
]
POSES = ["product", "hover", "shoot"]
PATHS = [f"workflows/fashion/{pose}_{d}.json" for d in DESIGNS for pose in POSES]
PATHS.append("workflows/fashion/upscale_4k.json")
# Marker id lets us find & replace our own block idempotently.
MARKER = "hanzo-workspace-seed"
paths_json = "[" + ", ".join('"%s"' % p for p in PATHS) + "]"
SEED_SCRIPT = (
f'<script id="{MARKER}">try{{'
f"if(localStorage.getItem('hanzoWorkspaceSeed')!=='{SEED_VERSION}'){{"
f"localStorage.setItem('Comfy.OpenWorkflowsPaths',JSON.stringify({paths_json}));"
f"localStorage.setItem('Comfy.ActiveWorkflowIndex','0');"
f"for(var i=sessionStorage.length-1;i>=0;i--){{var k=sessionStorage.key(i);"
f"if(k&&(k.indexOf('Comfy.OpenWorkflowsPaths:')===0"
f"||k.indexOf('Comfy.ActiveWorkflowIndex:')===0))sessionStorage.removeItem(k);}}"
f"localStorage.setItem('hanzoWorkspaceSeed','{SEED_VERSION}');"
f"}}}}catch(e){{}}</script>"
)
def main() -> None:
with open(INDEX, "r", encoding="utf-8") as f:
html = f.read()
# Remove any prior seed block (marked or the original unmarked variant).
html = re.sub(r'<script id="%s">.*?</script>' % re.escape(MARKER), "", html,
flags=re.DOTALL)
html = re.sub(r"<script>try\{if\(localStorage\.getItem\('hanzoWorkspaceSeed'\).*?</script>",
"", html, flags=re.DOTALL)
if MARKER not in html:
html = html.replace("<head>", "<head>" + SEED_SCRIPT, 1)
with open(INDEX, "w", encoding="utf-8") as f:
f.write(html)
print(f" Seeded workspace ({len(PATHS)} tabs, guard {SEED_VERSION}) into index.html")
if __name__ == "__main__":
main()