From f0781da998664bf7fb8cc86f474b7d825ad9a2a4 Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Sun, 5 Jul 2026 13:13:12 +0530 Subject: [PATCH 01/22] refactor!: rename Builder Variable to Builder Token "Variable" was confusing for users and LLMs alike; the doctype is the site's design-token registry, so name it that. Only the DocType and the label field (variable_name -> token_name) are renamed - token doc names (the CSS -- handles) are untouched, so every existing page's var(--id) references keep resolving byte-for-byte. Pre-model-sync patch handles the rename (verified on a virgin site: all rows migrate, old table dropped). The variables.css route becomes tokens.css; published HTML is generated live so no stored references break. The historic refactor_builder_variables patch keeps operating on the old names it targets (it runs before the rename) with only its import repointed. Co-Authored-By: Claude --- .../doctype/builder_page/builder_page.py | 6 +- .../__init__.py | 0 .../builder_token.js} | 2 +- .../builder_token.json} | 8 +-- .../builder_token.py} | 40 +++++------ .../test_builder_token.py} | 8 +-- .../patches/refactor_builder_variables.py | 2 +- ...ename_builder_variable_to_builder_token.py | 33 +++++++++ builder/export_import_standard_page.py | 18 ++--- builder/install.py | 6 +- builder/patches.txt | 4 +- builder/template_sync.py | 10 +-- builder/templates/generators/webpage.html | 4 +- builder/utils.py | 8 +-- .../{variables.css => tokens.css} | 0 .../{variables.py => tokens.py} | 2 +- frontend/components.d.ts | 5 +- frontend/src/components/BackgroundHandler.vue | 6 +- frontend/src/components/BuilderCanvas.vue | 4 +- frontend/src/components/BuilderLeftPanel.vue | 14 ++-- .../src/components/Controls/ColorInput.vue | 18 ++--- .../Controls/ColorPickerContent.vue | 4 +- ...uilderVariable.vue => NewBuilderToken.vue} | 30 ++++---- .../{VariableManager.vue => TokenManager.vue} | 69 +++++++++---------- frontend/src/data/builderToken.ts | 14 ++++ frontend/src/data/builderVariable.ts | 14 ---- frontend/src/types/doctypes.ts | 4 +- frontend/src/utils/builderBlockCopyPaste.ts | 18 ++--- frontend/src/utils/colorOptions.ts | 22 +++--- ...eBuilderVariable.ts => useBuilderToken.ts} | 62 ++++++++--------- 30 files changed, 234 insertions(+), 201 deletions(-) rename builder/builder/doctype/{builder_variable => builder_token}/__init__.py (100%) rename builder/builder/doctype/{builder_variable/builder_variable.js => builder_token/builder_token.js} (87%) rename builder/builder/doctype/{builder_variable/builder_variable.json => builder_token/builder_token.json} (93%) rename builder/builder/doctype/{builder_variable/builder_variable.py => builder_token/builder_token.py} (64%) rename builder/builder/doctype/{builder_variable/test_builder_variable.py => builder_token/test_builder_token.py} (77%) create mode 100644 builder/builder/patches/rename_builder_variable_to_builder_token.py rename builder/www/builder_assets/{variables.css => tokens.css} (100%) rename builder/www/builder_assets/{variables.py => tokens.py} (68%) rename frontend/src/components/Modals/{NewBuilderVariable.vue => NewBuilderToken.vue} (66%) rename frontend/src/components/Modals/{VariableManager.vue => TokenManager.vue} (93%) create mode 100644 frontend/src/data/builderToken.ts delete mode 100644 frontend/src/data/builderVariable.ts rename frontend/src/utils/{useBuilderVariable.ts => useBuilderToken.ts} (50%) diff --git a/builder/builder/doctype/builder_page/builder_page.py b/builder/builder/doctype/builder_page/builder_page.py index 36ec85680..578248e5e 100644 --- a/builder/builder/doctype/builder_page/builder_page.py +++ b/builder/builder/doctype/builder_page/builder_page.py @@ -355,11 +355,11 @@ def get_context(self, context): if context.preview: context.disable_auto_dark_mode = 0 - # /builder_assets/variables.css is a rendered route, not a real file, so + # /builder_assets/tokens.css is a rendered route, not a real file, so # the preview/PDF generator can't fetch it. Inline the variables instead. - from builder.builder.doctype.builder_variable.builder_variable import get_variables_css + from builder.builder.doctype.builder_token.builder_token import get_variables_css - context.inline_variables_css = get_variables_css() + context.inline_tokens_css = get_variables_css() # Honour the dark/light mode the editor previews in (canvasDarkMode), so the # initial server render matches it instead of falling back to the OS scheme. scheme = frappe.form_dict.get("prefers_color_scheme") diff --git a/builder/builder/doctype/builder_variable/__init__.py b/builder/builder/doctype/builder_token/__init__.py similarity index 100% rename from builder/builder/doctype/builder_variable/__init__.py rename to builder/builder/doctype/builder_token/__init__.py diff --git a/builder/builder/doctype/builder_variable/builder_variable.js b/builder/builder/doctype/builder_token/builder_token.js similarity index 87% rename from builder/builder/doctype/builder_variable/builder_variable.js rename to builder/builder/doctype/builder_token/builder_token.js index d38eda6ff..92cf72f6e 100644 --- a/builder/builder/doctype/builder_variable/builder_variable.js +++ b/builder/builder/doctype/builder_token/builder_token.js @@ -1,7 +1,7 @@ // Copyright (c) 2025, Frappe Technologies Pvt Ltd and contributors // For license information, please see license.txt -frappe.ui.form.on("Builder Variable", { +frappe.ui.form.on("Builder Token", { refresh: function (frm) { // Only show is_standard field in developer mode frm.get_field("is_standard").toggle(frappe.boot.developer_mode); diff --git a/builder/builder/doctype/builder_variable/builder_variable.json b/builder/builder/doctype/builder_token/builder_token.json similarity index 93% rename from builder/builder/doctype/builder_variable/builder_variable.json rename to builder/builder/doctype/builder_token/builder_token.json index da5305134..2917c85e0 100644 --- a/builder/builder/doctype/builder_variable/builder_variable.json +++ b/builder/builder/doctype/builder_token/builder_token.json @@ -5,7 +5,7 @@ "engine": "InnoDB", "field_order": [ "is_standard", - "variable_name", + "token_name", "group", "type", "value", @@ -28,7 +28,7 @@ "reqd": 1 }, { - "fieldname": "variable_name", + "fieldname": "token_name", "fieldtype": "Data", "in_list_view": 1, "label": "Variable Name", @@ -61,7 +61,7 @@ "modified": "2026-05-24 12:00:00.000000", "modified_by": "Administrator", "module": "Builder", - "name": "Builder Variable", + "name": "Builder Token", "naming_rule": "By script", "owner": "Administrator", "permissions": [ @@ -89,5 +89,5 @@ "sort_field": "creation", "sort_order": "DESC", "states": [], - "title_field": "variable_name" + "title_field": "token_name" } diff --git a/builder/builder/doctype/builder_variable/builder_variable.py b/builder/builder/doctype/builder_token/builder_token.py similarity index 64% rename from builder/builder/doctype/builder_variable/builder_variable.py rename to builder/builder/doctype/builder_token/builder_token.py index cac5c9d4a..7c616c881 100644 --- a/builder/builder/doctype/builder_variable/builder_variable.py +++ b/builder/builder/doctype/builder_token/builder_token.py @@ -10,7 +10,7 @@ from frappe.website.utils import delete_page_cache -class BuilderVariable(Document): +class BuilderToken(Document): # begin: auto-generated types # This code is auto-generated. Do not modify anything in this block. @@ -24,7 +24,7 @@ class BuilderVariable(Document): is_standard: DF.Check type: DF.Literal["Color", "Dimension"] value: DF.Data - variable_name: DF.Data + token_name: DF.Data # end: auto-generated types def autoname(self): @@ -32,37 +32,37 @@ def autoname(self): self.name = str(uuid.uuid4()) def after_insert(self): - clear_builder_variable_cache() + clear_builder_token_cache() def on_update(self): - clear_builder_variable_cache() + clear_builder_token_cache() if self.is_standard: export_to_files( - record_list=[["Builder Variable", self.name, "builder_variable"]], record_module="builder" + record_list=[["Builder Token", self.name, "builder_token"]], record_module="builder" ) if self.has_value_changed("is_standard") and not self.is_standard: - delete_folder("builder", "builder_variable", self.name) + delete_folder("builder", "builder_token", self.name) def on_trash(self): - clear_builder_variable_cache() + clear_builder_token_cache() if self.is_standard: - delete_folder("builder", "builder_variable", self.name) + delete_folder("builder", "builder_token", self.name) @redis_cache(ttl=10 * 24 * 3600) def get_css_variables(): - builder_variables = frappe.get_all("Builder Variable", fields=["name", "value", "dark_value"]) + builder_tokens = frappe.get_all("Builder Token", fields=["name", "value", "dark_value"]) css_variables = {} dark_mode_css_variables = {} - for builder_variable in builder_variables: - if not builder_variable.value: + for builder_token in builder_tokens: + if not builder_token.value: continue - key = f"--{builder_variable.name}" - css_variables[key] = builder_variable.value - if builder_variable.dark_value: - dark_mode_css_variables[key] = builder_variable.dark_value + key = f"--{builder_token.name}" + css_variables[key] = builder_token.value + if builder_token.dark_value: + dark_mode_css_variables[key] = builder_token.dark_value return css_variables, dark_mode_css_variables @@ -70,10 +70,10 @@ def get_css_variables(): def get_variables_css() -> str: """Render the CSS variables as an inline `:root {...}` rule. - The /builder_assets/variables.css route is a dynamically rendered page, not a + The /builder_assets/tokens.css route is a dynamically rendered page, not a real file, so the preview/PDF generator can't fetch it (it blocks access to non-existent local paths). Preview rendering inlines this string instead of - linking the route. Mirrors www/builder_assets/variables.css.""" + linking the route. Mirrors www/builder_assets/tokens.css.""" css_variables, dark_mode_css_variables = get_css_variables() if not css_variables: return "" @@ -89,7 +89,7 @@ def get_variables_css() -> str: return ":root {\n" + "\n".join(declarations) + "\n}" -def clear_builder_variable_cache(doc=None, method=None): +def clear_builder_token_cache(doc=None, method=None): get_css_variables.clear_cache() - # bust the rendered page cache for /builder_assets/variables.css - delete_page_cache("builder_assets/variables.css") + # bust the rendered page cache for /builder_assets/tokens.css + delete_page_cache("builder_assets/tokens.css") diff --git a/builder/builder/doctype/builder_variable/test_builder_variable.py b/builder/builder/doctype/builder_token/test_builder_token.py similarity index 77% rename from builder/builder/doctype/builder_variable/test_builder_variable.py rename to builder/builder/doctype/builder_token/test_builder_token.py index d20d8d6c8..46491f5aa 100644 --- a/builder/builder/doctype/builder_variable/test_builder_variable.py +++ b/builder/builder/doctype/builder_token/test_builder_token.py @@ -11,18 +11,18 @@ IGNORE_TEST_RECORD_DEPENDENCIES = [] # eg. ["User"] -class UnitTestbuilderVariable(UnitTestCase): +class UnitTestbuilderToken(UnitTestCase): """ - Unit tests for builderVariable. + Unit tests for builderToken. Use this class for testing individual functions and methods. """ pass -class IntegrationTestbuilderVariable(IntegrationTestCase): +class IntegrationTestbuilderToken(IntegrationTestCase): """ - Integration tests for builderVariable. + Integration tests for builderToken. Use this class for testing interactions between multiple components. """ diff --git a/builder/builder/patches/refactor_builder_variables.py b/builder/builder/patches/refactor_builder_variables.py index f2b13dc7f..b1bb43014 100644 --- a/builder/builder/patches/refactor_builder_variables.py +++ b/builder/builder/patches/refactor_builder_variables.py @@ -3,7 +3,7 @@ import frappe -from builder.builder.doctype.builder_variable.builder_variable import get_css_variables +from builder.builder.doctype.builder_token.builder_token import get_css_variables from builder.utils import camel_case_to_kebab_case UUID_RE = re.compile(r"^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$") diff --git a/builder/builder/patches/rename_builder_variable_to_builder_token.py b/builder/builder/patches/rename_builder_variable_to_builder_token.py new file mode 100644 index 000000000..9f7abf69c --- /dev/null +++ b/builder/builder/patches/rename_builder_variable_to_builder_token.py @@ -0,0 +1,33 @@ +import frappe +from frappe.model.utils.rename_field import rename_field + + +def execute(): + """Builder Variable → Builder Token. Only the DocType and the label field are + renamed — token doc names (the CSS `--` handles) are untouched, so every + existing page's var(--id) references keep resolving.""" + if not frappe.db.exists("DocType", "Builder Variable"): + return + if frappe.db.exists("DocType", "Builder Token"): + merge_stale_builder_variables() + return + frappe.rename_doc("DocType", "Builder Variable", "Builder Token", force=True) + frappe.reload_doc("builder", "doctype", "builder_token") + rename_field("Builder Token", "variable_name", "token_name") + + +def merge_stale_builder_variables(): + """Both doctypes exist when a site ran the rename and later re-synced the old + model. Keep Builder Token, salvage rows only the old table has, drop the rest.""" + if frappe.db.table_exists("Builder Variable"): + frappe.db.sql( + """INSERT INTO `tabBuilder Token` + (name, creation, modified, modified_by, owner, docstatus, + token_name, type, value, dark_value, is_standard, `group`) + SELECT name, creation, modified, modified_by, owner, docstatus, + variable_name, type, value, dark_value, is_standard, `group` + FROM `tabBuilder Variable` bv + WHERE NOT EXISTS (SELECT 1 FROM `tabBuilder Token` bt WHERE bt.name = bv.name)""" + ) + frappe.delete_doc("DocType", "Builder Variable", ignore_missing=True, force=True) + frappe.db.sql_ddl("DROP TABLE IF EXISTS `tabBuilder Variable`") diff --git a/builder/export_import_standard_page.py b/builder/export_import_standard_page.py index 39b725905..97002a298 100644 --- a/builder/export_import_standard_page.py +++ b/builder/export_import_standard_page.py @@ -260,7 +260,7 @@ def copy_font_file(file_url, assets_path, target_app="builder"): def export_variables(variables, builder_files_path): - """Export Builder Variable records""" + """Export Builder Token records""" if not variables: return @@ -274,19 +274,19 @@ def export_variables(variables, builder_files_path): # Try to find the variable by name var_docs = frappe.get_all( - "Builder Variable", + "Builder Token", filters=[ - ["variable_name", "in", [var_name, db_name, var_name.replace("-", " ").title()]], + ["token_name", "in", [var_name, db_name, var_name.replace("-", " ").title()]], ], - fields=["name", "variable_name", "type", "value", "dark_value"], + fields=["name", "token_name", "type", "value", "dark_value"], ) if not var_docs: # Also try searching by the scrubbed name var_docs = frappe.get_all( - "Builder Variable", + "Builder Token", filters={"name": db_name}, - fields=["name", "variable_name", "type", "value", "dark_value"], + fields=["name", "token_name", "type", "value", "dark_value"], ) if not var_docs: @@ -295,15 +295,15 @@ def export_variables(variables, builder_files_path): var_doc = var_docs[0] var_config = { - "doctype": "Builder Variable", + "doctype": "Builder Token", "name": var_doc.name, - "variable_name": var_doc.variable_name, + "token_name": var_doc.token_name, "type": var_doc.type, "value": var_doc.value, "dark_value": var_doc.dark_value, } - safe_var_name = frappe.scrub(var_doc.variable_name) + safe_var_name = frappe.scrub(var_doc.token_name) var_dir = os.path.join(variables_path, safe_var_name) os.makedirs(var_dir, exist_ok=True) var_file_path = os.path.join(var_dir, f"{safe_var_name}.json") diff --git a/builder/install.py b/builder/install.py index 19e58352e..2479384d8 100644 --- a/builder/install.py +++ b/builder/install.py @@ -4,7 +4,7 @@ from builder.utils import ( add_composite_index_to_web_page_view, sync_block_templates, - sync_builder_variables, + sync_builder_tokens, sync_page_templates, ) @@ -14,7 +14,7 @@ def after_install(): create_new_folder("Fonts", "Home/Builder Uploads") sync_page_templates() sync_block_templates() - sync_builder_variables() + sync_builder_tokens() add_composite_index_to_web_page_view() sync_standard_builder_pages() @@ -22,7 +22,7 @@ def after_install(): def after_migrate(): sync_page_templates() sync_block_templates() - sync_builder_variables() + sync_builder_tokens() sync_standard_builder_pages() diff --git a/builder/patches.txt b/builder/patches.txt index 392fc318d..6b8857257 100644 --- a/builder/patches.txt +++ b/builder/patches.txt @@ -4,6 +4,7 @@ builder.builder.patches.rename_web_page_beta_to_builder_page builder.builder.patches.rename_web_page_component_to_builder_component execute:frappe.delete_doc("DocType", "Builder Page Library", ignore_missing=True, force=True) +builder.builder.patches.rename_builder_variable_to_builder_token [post_model_sync] builder.builder.doctype.builder_component.patches.set_component_id builder.builder.doctype.builder_page.patches.properly_extend_blocks_from_component @@ -14,4 +15,5 @@ builder.builder.patches.add_composite_index_to_web_page_view builder.builder.patches.refactor_builder_variables builder.builder.patches.reset_builder_page_clicks execute:frappe.call("builder.builder_analytics.enqueue_web_page_view_ingesion") -execute:frappe.call("builder.builder_analytics.setup_duckdb_table") \ No newline at end of file +execute:frappe.call("builder.builder_analytics.setup_duckdb_table") + diff --git a/builder/template_sync.py b/builder/template_sync.py index 3e5095bc5..0a3c8bdde 100644 --- a/builder/template_sync.py +++ b/builder/template_sync.py @@ -1,7 +1,7 @@ """Import/export of builder template groups. A template group is a set of highly-functional pages (landing, contact, ...) -that share one set of Builder Components and Builder Variables (the variable +that share one set of Builder Components and Builder Tokens (the variable `group` matches the template group). Fixtures live on disk at builder/builder/builder_templates//: @@ -263,14 +263,14 @@ def export_template_variables(group, variables_path): """Write fixtures for all variables of the group, pinning their uuid names so var(--) references in blocks survive the round-trip.""" for var in frappe.get_all( - "Builder Variable", + "Builder Token", filters={"group": group}, - fields=["name", "variable_name", "type", "value", "dark_value", "group"], + fields=["name", "token_name", "type", "value", "dark_value", "group"], ): var_config = { - "doctype": "Builder Variable", + "doctype": "Builder Token", "name": var.name, - "variable_name": var.variable_name, + "token_name": var.token_name, "type": var.type, "value": var.value, "dark_value": var.dark_value, diff --git a/builder/templates/generators/webpage.html b/builder/templates/generators/webpage.html index 21a95fc08..7f4bb2059 100644 --- a/builder/templates/generators/webpage.html +++ b/builder/templates/generators/webpage.html @@ -30,9 +30,9 @@ {% endfor %} {{ style }} {%- if preview -%} - + {%- else -%} - + {%- endif -%} {%- if custom_fonts -%} diff --git a/builder/utils.py b/builder/utils.py index b216421aa..99cef85d8 100644 --- a/builder/utils.py +++ b/builder/utils.py @@ -320,10 +320,10 @@ def sync_block_templates(): make_records(builder_block_template_path) -def sync_builder_variables(): - print("Syncing Builder Builder Variables") - builder_variable_path = frappe.get_module_path("builder", "builder_variable") - make_records(builder_variable_path) +def sync_builder_tokens(): + print("Syncing Builder Builder Tokens") + builder_token_path = frappe.get_module_path("builder", "builder_token") + make_records(builder_token_path) def make_records(path): diff --git a/builder/www/builder_assets/variables.css b/builder/www/builder_assets/tokens.css similarity index 100% rename from builder/www/builder_assets/variables.css rename to builder/www/builder_assets/tokens.css diff --git a/builder/www/builder_assets/variables.py b/builder/www/builder_assets/tokens.py similarity index 68% rename from builder/www/builder_assets/variables.py rename to builder/www/builder_assets/tokens.py index 87bfe9796..eff7554e2 100644 --- a/builder/www/builder_assets/variables.py +++ b/builder/www/builder_assets/tokens.py @@ -1,4 +1,4 @@ -from builder.builder.doctype.builder_variable.builder_variable import get_css_variables +from builder.builder.doctype.builder_token.builder_token import get_css_variables def get_context(context): diff --git a/frontend/components.d.ts b/frontend/components.d.ts index f58f7f4c7..253b14ee4 100644 --- a/frontend/components.d.ts +++ b/frontend/components.d.ts @@ -95,7 +95,7 @@ declare module 'vue' { MarginHandler: typeof import('./src/components/MarginHandler.vue')['default'] MiddleTruncate: typeof import('./src/components/MiddleTruncate.vue')['default'] NewBlockTemplate: typeof import('./src/components/Modals/NewBlockTemplate.vue')['default'] - NewBuilderVariable: typeof import('./src/components/Modals/NewBuilderVariable.vue')['default'] + NewBuilderToken: typeof import('./src/components/Modals/NewBuilderToken.vue')['default'] NumberArrows: typeof import('./src/components/Controls/NumberArrows.vue')['default'] NumberOptions: typeof import('./src/components/PropsOptions/NumberOptions.vue')['default'] ObjectEditor: typeof import('./src/components/ObjectEditor.vue')['default'] @@ -125,7 +125,6 @@ declare module 'vue' { PublishButton: typeof import('./src/components/PublishButton.vue')['default'] RangeInput: typeof import('./src/components/Controls/RangeInput.vue')['default'] RotationHandler: typeof import('./src/components/RotationHandler.vue')['default'] - RotationInput: typeof import('./src/components/Controls/RotationInput.vue')['default'] RouterLink: typeof import('vue-router')['RouterLink'] RouterView: typeof import('vue-router')['RouterView'] RouteTreeNode: typeof import('./src/components/RouteTreeNode.vue')['default'] @@ -145,11 +144,11 @@ declare module 'vue' { TemplatesDialog: typeof import('./src/components/Templates/TemplatesDialog.vue')['default'] TextBlock: typeof import('./src/components/TextBlock.vue')['default'] TextBlockBubbleMenu: typeof import('./src/components/TextBlockBubbleMenu.vue')['default'] + TokenManager: typeof import('./src/components/Modals/TokenManager.vue')['default'] TopClicksList: typeof import('./src/components/Settings/TopClicksList.vue')['default'] TopReferrersList: typeof import('./src/components/Settings/TopReferrersList.vue')['default'] TrackingDisabledNotice: typeof import('./src/components/Settings/TrackingDisabledNotice.vue')['default'] TransformPreview: typeof import('./src/components/TransformPreview.vue')['default'] - VariableManager: typeof import('./src/components/Modals/VariableManager.vue')['default'] VariantControl: typeof import('./src/components/Controls/VariantControl.vue')['default'] VersionHistory: typeof import('./src/components/VersionHistory.vue')['default'] VisibilityInput: typeof import('./src/components/VisibilityInput.vue')['default'] diff --git a/frontend/src/components/BackgroundHandler.vue b/frontend/src/components/BackgroundHandler.vue index 504c31755..23609ac12 100644 --- a/frontend/src/components/BackgroundHandler.vue +++ b/frontend/src/components/BackgroundHandler.vue @@ -139,12 +139,12 @@ import useBuilderStore from "@/stores/builderStore"; import blockController from "@/utils/blockController"; import { cssUrl } from "@/utils/helpers"; import { getOptimizeButtonText, optimizeImage, shouldShowOptimizeButton } from "@/utils/imageUtils"; -import { useBuilderVariable } from "@/utils/useBuilderVariable"; +import { useBuilderToken } from "@/utils/useBuilderToken"; import { FileUploader, Popover, Switch } from "frappe-ui"; import { computed, defineComponent, h, ref, watch } from "vue"; const builderStore = useBuilderStore(); -const { getVariableName, resolveVariableValue, variables } = useBuilderVariable(); +const { getVariableName, resolveVariableValue, variables } = useBuilderToken(); // wraps Input to style the value like ColorInput does when it displays a variable name const BackgroundInput = defineComponent({ @@ -155,7 +155,7 @@ const BackgroundInput = defineComponent({ const showsVariableName = computed(() => { return ( !!props.modelValue && - variables.value.some((builderVariable) => builderVariable.variable_name === props.modelValue) + variables.value.some((builderToken) => builderToken.token_name === props.modelValue) ); }); return () => diff --git a/frontend/src/components/BuilderCanvas.vue b/frontend/src/components/BuilderCanvas.vue index abfe8edc6..4bc21901f 100644 --- a/frontend/src/components/BuilderCanvas.vue +++ b/frontend/src/components/BuilderCanvas.vue @@ -134,7 +134,7 @@ import { } from "@/utils/scriptSandbox"; import { useBlockEventHandlers } from "@/utils/useBlockEventHandlers"; import { useBlockSelection } from "@/utils/useBlockSelection"; -import { useBuilderVariable } from "@/utils/useBuilderVariable"; +import { useBuilderToken } from "@/utils/useBuilderToken"; import { useCanvasDropZone } from "@/utils/useCanvasDropZone"; import { useCanvasEvents } from "@/utils/useCanvasEvents"; import { useCanvasMarqueeSelection } from "@/utils/useCanvasMarqueeSelection"; @@ -151,7 +151,7 @@ const builderStore = useBuilderStore(); const pageStore = usePageStore(); const canvasId = `builder-canvas-${useId()}`; -const { cssVariables, darkCssVariables } = useBuilderVariable(); +const { cssVariables, darkCssVariables } = useBuilderToken(); const variables = computed(() => { return { diff --git a/frontend/src/components/BuilderLeftPanel.vue b/frontend/src/components/BuilderLeftPanel.vue index 51a86aaef..5859c5911 100644 --- a/frontend/src/components/BuilderLeftPanel.vue +++ b/frontend/src/components/BuilderLeftPanel.vue @@ -18,7 +18,7 @@ size="md" :variant=" builderStore.leftPanelActiveTab === option.value || - (showVariableManager && option.value === 'variables') + (showTokenManager && option.value === 'variables') ? 'subtle' : 'ghost' " @@ -77,13 +77,13 @@ - + diff --git a/frontend/src/components/Controls/FontInputActions.vue b/frontend/src/components/Controls/FontInputActions.vue new file mode 100644 index 000000000..6636e1096 --- /dev/null +++ b/frontend/src/components/Controls/FontInputActions.vue @@ -0,0 +1,44 @@ + + + diff --git a/frontend/src/types/doctypes.ts b/frontend/src/types/doctypes.ts index 384f8c39c..b2843d98e 100644 --- a/frontend/src/types/doctypes.ts +++ b/frontend/src/types/doctypes.ts @@ -56,7 +56,7 @@ export interface UserFont extends DocType { // Last updated: 2026-05-24 12:00:00.000000 export interface BuilderToken extends DocType { /** Type: Select */ - type?: 'Color' | 'Dimension'; + type?: 'Color' | 'Dimension' | 'Font'; /** Value: Data */ value: string; /** Variable Name: Data */ From 8e64723af8a207d4c93f9567a215f02e7748fd87 Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Thu, 16 Jul 2026 14:46:07 +0530 Subject: [PATCH 08/22] feat: keep old Builder Variable entry points working /builder_assets/variables.css serves the same token CSS as tokens.css, so pages published (or cached) before the rename keep their stylesheet. builder.utils.sync_builder_variables stays callable as an alias. Co-Authored-By: Claude --- builder/utils.py | 4 ++++ builder/www/builder_assets/variables.css | 3 +++ builder/www/builder_assets/variables.py | 5 +++++ 3 files changed, 12 insertions(+) create mode 100644 builder/www/builder_assets/variables.css create mode 100644 builder/www/builder_assets/variables.py diff --git a/builder/utils.py b/builder/utils.py index 99cef85d8..d9275ac73 100644 --- a/builder/utils.py +++ b/builder/utils.py @@ -326,6 +326,10 @@ def sync_builder_tokens(): make_records(builder_token_path) +# Compat alias, external scripts may still call the old name +sync_builder_variables = sync_builder_tokens + + def make_records(path): if not os.path.isdir(path): return diff --git a/builder/www/builder_assets/variables.css b/builder/www/builder_assets/variables.css new file mode 100644 index 000000000..792b25cb6 --- /dev/null +++ b/builder/www/builder_assets/variables.css @@ -0,0 +1,3 @@ +{%- if css_variables -%}:root { +{%- for key, value in css_variables.items() %}{%- set dark_value = (dark_mode_css_variables or {}).get(key) %}{{ key }}: {% if dark_value is not none and dark_value != value %}light-dark({{ value }}, {{ dark_value }}){% else %}{{ value }}{% endif %}; +{%- endfor %}}{%- endif -%} \ No newline at end of file diff --git a/builder/www/builder_assets/variables.py b/builder/www/builder_assets/variables.py new file mode 100644 index 000000000..33137ce77 --- /dev/null +++ b/builder/www/builder_assets/variables.py @@ -0,0 +1,5 @@ +# Compat route: pages published before the Builder Token rename link +# /builder_assets/variables.css. Serves the same CSS as tokens.css. +from builder.www.builder_assets.tokens import get_context + +__all__ = ["get_context"] From c3429ccc28ea1711856d1ae3a35e7453ba096faa Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Thu, 16 Jul 2026 16:26:16 +0530 Subject: [PATCH 09/22] fix: CSV import honors the Type column The hardcoded type: "Color" sat after the conditional spread of the parsed type, so every imported row became a Color token regardless of its Type cell. Parse the type case-insensitively (Color/Font/Dimension, defaulting to Color) and include typed rows in the sample CSV. Co-Authored-By: Claude --- .../src/components/Modals/TokenManager.vue | 20 +++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/frontend/src/components/Modals/TokenManager.vue b/frontend/src/components/Modals/TokenManager.vue index 495ebc1b6..3ddb16ad7 100644 --- a/frontend/src/components/Modals/TokenManager.vue +++ b/frontend/src/components/Modals/TokenManager.vue @@ -903,17 +903,19 @@ const parseCSVAndAddVariables = async (csvText: string) => { // a present-but-blank cell clears the group explicitly const groupValue = groupIndex !== -1 ? values[groupIndex] : undefined; - const typeValue = typeIndex !== -1 ? values[typeIndex] : undefined; + const rawType = (typeIndex !== -1 && values[typeIndex]) || ""; + const typeValue = (["Color", "Font", "Dimension"].find( + (t) => t.toLowerCase() === rawType.toLowerCase(), + ) || "Color") as BuilderToken["type"]; if (variableName && lightValue) { const existing = variables.value.find((v) => v.token_name === variableName); if (!existing) { newVariables.push({ - ...(typeValue ? { type: typeValue as BuilderToken["type"] } : {}), token_name: variableName, value: lightValue, dark_value: darkValue, group: groupValue, - type: "Color", + type: typeValue, }); } else if (existing.is_standard) { // standard variables are read-only in the manager; skip them here too @@ -1012,11 +1014,13 @@ const parseCSVAndAddVariables = async (csvText: string) => { const downloadSampleCSV = () => { const sampleData = [ - ["Variable Name", "Light Mode", "Dark Mode", "Group"], - ["primary-color", "#3b82f6", "#60a5fa", "Brand"], - ["secondary-color", "#10b981", "#34d399", "Brand"], - ["background-color", "#ffffff", "#1f2937", "Surface"], - ["text-color", "#111827", "#f9fafb", ""], + ["Variable Name", "Light Mode", "Dark Mode", "Group", "Type"], + ["primary-color", "#3b82f6", "#60a5fa", "Brand", "Color"], + ["secondary-color", "#10b981", "#34d399", "Brand", "Color"], + ["background-color", "#ffffff", "#1f2937", "Surface", "Color"], + ["text-color", "#111827", "#f9fafb", "", "Color"], + ["heading-font", "Fraunces", "", "Brand", "Font"], + ["section-gap", "96px", "", "Layout", "Dimension"], ]; const csvContent = sampleData.map((row) => row.join(",")).join("\n"); From 4d041974b62ce6f28d2f95b416ae49d516923bc9 Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Thu, 16 Jul 2026 16:50:13 +0530 Subject: [PATCH 10/22] refactor: use frappe-ui TabButtons and Button in the Design System manager The type tabs were hand-rolled buttons and the copy affordance a bare