From 0ff24b1534044ff8e4d486216b56fa61049b0c7c Mon Sep 17 00:00:00 2001 From: GreenEclipse Date: Tue, 18 Aug 2026 01:49:44 +0200 Subject: [PATCH] Surface Slovenian A1 content in prompt page; purge English defaults MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three bugs hid the split A1 data behind English junk: 1. /api/auth/me company was only {id, name} — me.company.language never existed, so the prompt/formula pages ALWAYS fell back to English as the primary content language and never showed the Slovenian ("sl") prompt sections by default. handleMe now enriches the active company with language + content_languages from the companies table. 2. The unified prompt page injected English default texts: meta title / meta description fields were pre-filled via getDefaultMetaTitle/ Description (and silently SAVED on Save), new description sections started with English instructions, and type changes overwrote user text with English defaults. All removed — empty stays empty (placeholders only), stored tenant content is shown exactly as-is, meta-only templates now save (sections no longer required). 3. Sync A1 / repair left English formula artifacts on categories with no seed content ("Audio video" etc.): earlier repairs backfilled DefaultRetailTitleFormula and cats.json-style English description sections onto every category. Repair now (a) never invents a default title formula — formulas derive ONLY from legacy naming rules — and (b) with the seed as source of truth clears title_template and description_template on non-seed categories (new title/description_template_cleared counters). Locally verified end-to-end: corrupted Platform Demo Monitorji (legacy combined blob) + Audio video (English junk), called the real admin POST /companies/{id}/sync-a1 as the platform admin — Monitorji restored to the exact split Slovenian Title/Description/Meta + formulas + Slovenian meta title/description, Audio video fully cleared; repair idempotent (0 on re-run); me.company.language returns sl for A1; zero English-default templates remain in A1 + Platform Demo. Co-Authored-By: Claude Fable 5 --- apps/api/internal/catalog/prompt_repair.go | 44 +++++++++- apps/api/internal/httpapi/auth_handlers.go | 18 ++++ .../[categoryId]/prompt/+page.svelte | 83 ++++++++----------- 3 files changed, 94 insertions(+), 51 deletions(-) diff --git a/apps/api/internal/catalog/prompt_repair.go b/apps/api/internal/catalog/prompt_repair.go index c14cea6..4f33a33 100644 --- a/apps/api/internal/catalog/prompt_repair.go +++ b/apps/api/internal/catalog/prompt_repair.go @@ -37,6 +37,10 @@ type RepairCategoryEnhancePromptsResult struct { FallbackTemplate int `json:"fallback_template"` TitleTemplateBackfill int `json:"title_template_backfill"` DescTemplateBackfill int `json:"description_template_backfill"` + // *_Cleared count non-seed categories whose leftover formula artifacts + // (English defaults from earlier repairs / cats.json / migration) were removed. + TitleTemplateCleared int `json:"title_template_cleared"` + DescTemplateCleared int `json:"description_template_cleared"` ByCompany map[string]int `json:"by_company"` DryRun bool `json:"dry_run"` SeedPath string `json:"seed_path,omitempty"` @@ -524,7 +528,19 @@ func repairCompanyCategoryEnhancePrompts( if needDesc && strings.TrimSpace(legacyParts.DescriptionRules) == "" && strings.TrimSpace(legacyParts.MetaRules) == "" { needDesc = false } - if !needPrompt && !needTitle && !needDesc { + // Never invent a default title formula: formulas are derived ONLY from + // legacy naming rules. (Earlier repairs backfilled DefaultRetailTitleFormula + // with English labels onto every category — that junk gets cleared below.) + if strings.TrimSpace(titleRules) == "" { + needTitle = false + } + // Seed is the source of truth: categories with no seed/legacy content must + // not keep formula artifacts (English default sections/formulas from + // earlier repairs, cats.json seeding, or migration). + noLegacyContent := seedLegacy == "" && !legacyParts.WasLegacy && strings.TrimSpace(titleRules) == "" + clearTitleTpl := forceFromSeed && noLegacyContent && jsonbTemplatePresent(titleTpl) + clearDescTpl := forceFromSeed && noLegacyContent && jsonbTemplatePresent(descTpl) + if !needPrompt && !needTitle && !needDesc && !clearTitleTpl && !clearDescTpl { if company.HasAnyPrompt(m) { out.AlreadyOK++ } else { @@ -564,6 +580,12 @@ func repairCompanyCategoryEnhancePrompts( if needDesc { out.DescTemplateBackfill++ } + if clearTitleTpl { + out.TitleTemplateCleared++ + } + if clearDescTpl { + out.DescTemplateCleared++ + } updatedHere++ continue } @@ -640,6 +662,26 @@ func repairCompanyCategoryEnhancePrompts( } } + if clearTitleTpl { + if _, err := pool.Exec(ctx, ` + UPDATE categories + SET title_template = NULL, updated_at = now() + WHERE id = $1 AND company_id = $2`, id, companyID); err != nil { + return updatedHere, fmt.Errorf("clear title_template category=%s: %w", id, err) + } + out.TitleTemplateCleared++ + } + + if clearDescTpl { + if _, err := pool.Exec(ctx, ` + UPDATE categories + SET description_template = NULL, updated_at = now() + WHERE id = $1 AND company_id = $2`, id, companyID); err != nil { + return updatedHere, fmt.Errorf("clear description_template category=%s: %w", id, err) + } + out.DescTemplateCleared++ + } + out.Updated++ updatedHere++ } diff --git a/apps/api/internal/httpapi/auth_handlers.go b/apps/api/internal/httpapi/auth_handlers.go index 8832bf8..a0ed8e6 100644 --- a/apps/api/internal/httpapi/auth_handlers.go +++ b/apps/api/internal/httpapi/auth_handlers.go @@ -340,6 +340,24 @@ func (s *Server) handleMe(w http.ResponseWriter, r *http.Request) { out["company"] = c } } + // Enrich with content language settings: the dashboard (category prompt / + // formula pages, language switchers) reads me.company.language as the + // primary content language — auth.Company alone is only {id, name}, + // which silently fell back to English for every tenant. + if c, ok := out["company"].(auth.Company); ok { + var lang string + var contentLangs []string + if err := s.Pool.QueryRow(r.Context(), ` + SELECT COALESCE(language, 'en'), COALESCE(content_languages, '{}') + FROM companies WHERE id = $1`, cid).Scan(&lang, &contentLangs); err == nil { + out["company"] = map[string]any{ + "id": c.ID, + "name": c.Name, + "language": lang, + "content_languages": contentLangs, + } + } + } if credits, err := s.Billing.CreditsOverview(r.Context(), cid, s.Config.LowCreditsThreshold); err == nil { out["credits"] = credits } diff --git a/apps/web/src/routes/categories/[categoryId]/prompt/+page.svelte b/apps/web/src/routes/categories/[categoryId]/prompt/+page.svelte index 343aea3..b2aa82a 100644 --- a/apps/web/src/routes/categories/[categoryId]/prompt/+page.svelte +++ b/apps/web/src/routes/categories/[categoryId]/prompt/+page.svelte @@ -10,9 +10,6 @@ buildTemplateToSave, elementId, generatePreviewElements, - getDefaultInstructions, - getDefaultMetaDescription, - getDefaultMetaTitle, parseDescriptionTemplate, parseTemplateToFormula } from "$lib/categories/formula"; @@ -137,10 +134,13 @@ let confirmOpen = $state(false); let itemToDelete = $state<{ type: string; id: string } | null>(null); - // Description formula (categories.description_template) — same editor as /description-formula. + // Description formula (categories.description_template) — same editor as + // /description-formula, but NEVER pre-filled with English default texts: + // empty stays empty (placeholders only) so stored Slovenian/tenant content + // is shown exactly as-is and defaults are never silently saved. let descSections = $state([]); - let metaTitle = $state(getDefaultMetaTitle()); - let metaDescription = $state(getDefaultMetaDescription()); + let metaTitle = $state(""); + let metaDescription = $state(""); let dragIndex = $state(null); const langLabel = $derived( @@ -224,8 +224,14 @@ titleFormula = parseTemplateToFormula(cat.title_template, customVariables); const parsedDesc = parseDescriptionTemplate(cat.description_template); descSections = parsedDesc.sections; - metaTitle = parsedDesc.metaTitle; - metaDescription = parsedDesc.metaDescription; + // Raw stored values only — parseDescriptionTemplate injects English + // defaults for missing meta fields, which must never be displayed or saved. + const rawTpl = (cat.description_template ?? {}) as { + metaTitle?: unknown; + metaDescription?: unknown; + }; + metaTitle = typeof rawTpl.metaTitle === "string" ? rawTpl.metaTitle : ""; + metaDescription = typeof rawTpl.metaDescription === "string" ? rawTpl.metaDescription : ""; } function onLangChange(code: string) { @@ -374,10 +380,9 @@ // ----- Description formula editing ----- function addDescSection() { - descSections = [ - ...descSections, - { id: crypto.randomUUID(), type: "p", instructions: getDefaultInstructions("p") } - ]; + // New sections start EMPTY — placeholder text guides the user; no + // English default instructions are ever inserted into stored content. + descSections = [...descSections, { id: crypto.randomUUID(), type: "p", instructions: "" }]; } function removeDescSection(id: string) { @@ -385,14 +390,9 @@ } function updateDescSection(id: string, updates: Partial) { - descSections = descSections.map((section) => { - if (section.id !== id) return section; - const next = { ...section, ...updates }; - if (updates.type && updates.type !== section.type) { - next.instructions = getDefaultInstructions(updates.type); - } - return next; - }); + descSections = descSections.map((section) => + section.id === id ? { ...section, ...updates } : section + ); } function reorderDescSections(from: number, to: number) { @@ -403,11 +403,14 @@ } function buildDescriptionTemplate() { - if (descSections.length === 0) return null; + const keep = descSections.filter((s) => s.instructions.trim() || s.exportId?.trim()); + const mt = metaTitle.trim(); + const md = metaDescription.trim(); + if (keep.length === 0 && !mt && !md) return null; return { - sections: descSections, - metaTitle: metaTitle.trim() || undefined, - metaDescription: metaDescription.trim() || undefined + sections: keep, + metaTitle: mt || undefined, + metaDescription: md || undefined }; } @@ -820,19 +823,9 @@ rows="2" placeholder={i18n.t("categories.seoTitlePlaceholder")} > -
-

- {i18n.t("categories.metaTitleHint")} -

- -
+

+ {i18n.t("categories.metaTitleHint")} +

@@ -843,19 +836,9 @@ rows="3" placeholder={i18n.t("categories.seoDescriptionPlaceholder")} > -
-

- {i18n.t("categories.metaDescriptionHint")} -

- -
+

+ {i18n.t("categories.metaDescriptionHint")} +