Auto-derive session cookie domain; AI prompt page = formula builder hub

Session (no env needed):
- SESSION_COOKIE_DOMAIN env removed. Config.SessionCookieParentDomain()
  derives the cookie Domain from WEB_ORIGIN + PUBLIC_API_URL, which the
  API already requires: sibling hosts of one parent (descrybe.io +
  api.descrybe.io) share the parent domain so SvelteKit SSR (/admin
  gate, user switching) receives the session cookie; localhost, IPs,
  same-host, and unrelated hosts stay host-only. Deploying the new build
  is the whole fix — nothing to configure.

AI generation prompt page:
- Each section now embeds its formula editor next to the per-language
  prompt instructions: Title = full title formula builder (preview,
  elements, separator, variable selector, custom variables), Description
  = description formula sections editor (type + instructions + export
  id, drag reorder), Meta = meta title / meta description formula
  fields. One Save writes categories.prompt + title_template +
  description_template together; Assign copies all three to the
  selected categories.
- New $lib/categories/formula-variables.ts loads every usable field for
  the builder: custom variables (/api/variables), company attributes
  (/api/attributes — attribute_key, name, unit, example), and standard
  fields (/api/standard-fields). Used by both the prompt page and the
  title-formula page (which previously ignored attributes).

Verified locally: svelte-check clean for changed files, unit tests pass,
and the full save contract exercised over HTTP as the page does it
(login → load variables/attributes/standard-fields → PATCH prompt +
title-formula + description-formula → round-trip read), then the test
category restored via repair-category-prompts.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-18 01:12:23 +02:00
co-authored by Claude Fable 5
parent 9a839d6d13
commit d03c2a5c57
7 changed files with 657 additions and 62 deletions
+28
View File
@@ -528,3 +528,31 @@ func TestValidateProcessingPollInterval(t *testing.T) {
t.Fatal("expected PROCESSING_POLL_INTERVAL > 0")
}
}
func TestSessionCookieParentDomain(t *testing.T) {
t.Parallel()
cases := []struct {
name string
web string
api string
want string
}{
{name: "prod_split", web: "https://descrybe.io", api: "https://api.descrybe.io", want: "descrybe.io"},
{name: "prod_split_reversed", web: "https://app.descrybe.io", api: "https://descrybe.io", want: "descrybe.io"},
{name: "sibling_subdomains", web: "https://app.descrybe.io", api: "https://api.descrybe.io", want: "descrybe.io"},
{name: "localhost_ports", web: "http://localhost:28472", api: "http://localhost:28471", want: ""},
{name: "loopback_ip", web: "http://127.0.0.1:28472", api: "http://127.0.0.1:28471", want: ""},
{name: "same_host", web: "https://descrybe.io", api: "https://descrybe.io", want: ""},
{name: "unrelated_hosts", web: "https://descrybe.io", api: "https://example.com", want: ""},
{name: "empty_api", web: "https://descrybe.io", api: "", want: ""},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
c := Config{WebOrigin: tc.web, PublicAPIURL: tc.api}
if got := c.SessionCookieParentDomain(); got != tc.want {
t.Fatalf("web=%q api=%q got %q want %q", tc.web, tc.api, got, tc.want)
}
})
}
}