1/** 2 * Cross-step project state â sessionStorage cache + Supabase persistence. 3 */ 4(function (global) { 5 const KEYS = { 6 url: 'pc:project-url', 7 company: 'pc:project-company', 8 offer: 'pc:project-offer', 9 slug: 'pc:project-slug', 10 scrape: 'pc:scrape', 11 draftBrief: 'pc:draft-brief', 12 approvedBrief: 'pc:approved-brief', 13 briefApproved: 'pc:brief-approved', 14 briefingSeen: 'pc:briefing-seen', 15 generateSeen: 'pc:generate-seen', 16 projectMode: 'pc:project-mode', 17 flowMode: 'pc:flow-mode', 18 assetsApprovedGlobal: 'pc:assets-approved', 19 imageryApprovedGlobal: 'pc:imagery-approved' 20 }; 21 22 function slugify(value) { 23 return String(value || 'project') 24 .toLowerCase() 25 .replace(/[^a-z0-9]+/g, '-') 26 .replace(/^-+|-+$/g, '') 27 .slice(0, 48) || 'project'; 28 } 29 30 function readJson(key) { 31 try { 32 const raw = sessionStorage.getItem(key); 33 return raw ? JSON.parse(raw) : null; 34 } catch { 35 return null; 36 } 37 } 38 39 function writeJson(key, value) { 40 sessionStorage.setItem(key, JSON.stringify(value)); 41 } 42 43 function getUrl() { 44 return sessionStorage.getItem(KEYS.url) || ''; 45 } 46 47 function setUrl(url) { 48 sessionStorage.setItem(KEYS.url, String(url || '').trim()); 49 } 50 51 function getScrape() { 52 return readJson(KEYS.scrape); 53 } 54 55 function setScrape(scrape) { 56 if (scrape) writeJson(KEYS.scrape, scrape); 57 else sessionStorage.removeItem(KEYS.scrape); 58 } 59 60 function pickAssetArray(storedVal, fallbackVal) { 61 if (Array.isArray(storedVal) && storedVal.length) return storedVal; 62 if (Array.isArray(fallbackVal) && fallbackVal.length) return fallbackVal; 63 if (Array.isArray(storedVal)) return storedVal; 64 return Array.isArray(fallbackVal) ? fallbackVal : []; 65 } 66 67 function scrapeMetaFromScrape(scrape) { 68 if (!scrape) return {}; 69 return { 70 url: scrape.url || getUrl(), 71 title: scrape.title || '', 72 description: scrape.description || '', 73 manual: Boolean(scrape.manual), 74 scrapedAt: scrape.scrapedAt || null 75 }; 76 } 77 78 function brandKitFromScrape(scrape) { 79 if (!scrape) return null; 80 return { 81 colors: Array.isArray(scrape.colors) ? scrape.colors.map((c) => ({ ...c })) : [], 82 logos: Array.isArray(scrape.logos) ? scrape.logos.map((l) => ({ ...l })) : [], 83 pageImagery: Array.isArray(scrape.imagery) ? scrape.imagery.map((i) => ({ ...i })) : [], 84 scrapeMeta: scrapeMetaFromScrape(scrape) 85 }; 86 } 87 88 function getCompany() { 89 return sessionStorage.getItem(KEYS.company) || ''; 90 } 91 92 function setCompany(name) { 93 if (name) sessionStorage.setItem(KEYS.company, name); 94 } 95 96 function getOffer() { 97 return sessionStorage.getItem(KEYS.offer) || ''; 98 } 99 100 function setOffer(name) { 101 if (name) sessionStorage.setItem(KEYS.offer, name); 102 } 103 104 function getSlug() { 105 const stored = sessionStorage.getItem(KEYS.slug); 106 if (stored) return stored; 107 const fromCompany = slugify(getCompany()); 108 if (fromCompany !== 'project') { 109 sessionStorage.setItem(KEYS.slug, fromCompany); 110 return fromCompany; 111 } 112 return 'project'; 113 } 114 115 function setSlug(slug) { 116 sessionStorage.setItem(KEYS.slug, slugify(slug)); 117 } 118 119 function syncMetaFromBrief(brief) { 120 if (!brief) return; 121 if (brief.companyName) { 122 setCompany(brief.companyName); 123 setSlug(brief.companyName); 124 } 125 if (brief.offerName) setOffer(brief.offerName); 126 if (brief.url) setUrl(brief.url); 127 } 128 129 function stripBriefForStorage(brief) { 130 if (!brief) return null; 131 const { scrape, approved, ...rest } = brief; 132 return rest; 133 } 134 135 function getDraftBrief() { 136 return readJson(KEYS.draftBrief); 137 } 138 139 function setDraftBrief(brief) { 140 if (!brief) { 141 sessionStorage.removeItem(KEYS.draftBrief); 142 return; 143 } 144 writeJson(KEYS.draftBrief, stripBriefForStorage(brief)); 145 } 146 147 function isBriefApproved() { 148 return sessionStorage.getItem(KEYS.briefApproved) === '1'; 149 } 150 151 function getApprovedBrief() { 152 return readJson(KEYS.approvedBrief); 153 } 154 155 function setApprovedBrief(brief) { 156 if (!brief) { 157 sessionStorage.removeItem(KEYS.approvedBrief); 158 sessionStorage.removeItem(KEYS.briefApproved); 159 return; 160 } 161 writeJson(KEYS.approvedBrief, { ...stripBriefForStorage(brief), approved: true }); 162 sessionStorage.setItem(KEYS.briefApproved, '1'); 163 syncMetaFromBrief(brief); 164 } 165 166 function getProjectMode() { 167 return sessionStorage.getItem(KEYS.projectMode) || 'ai'; 168 } 169 170 function setProjectMode(mode) { 171 sessionStorage.setItem(KEYS.projectMode, mode === 'demo' ? 'demo' : 'ai'); 172 } 173 174 function isDemoMode() { 175 return getProjectMode() === 'demo'; 176 } 177 178 function getFlowMode() { 179 return sessionStorage.getItem(KEYS.flowMode) || 'consolidated'; 180 } 181 182 function setFlowMode(mode) { 183 const next = mode === 'layered' ? 'layered' : 'consolidated'; 184 sessionStorage.setItem(KEYS.flowMode, next); 185 } 186 187 function isConsolidatedFlow() { 188 return getFlowMode() !== 'layered'; 189 } 190 191 async function recordWizardStep(step, action, detail = {}) { 192 if (!global.PathCreativesSupabase) return null; 193 try { 194 await global.PathCreativesSupabase.init(); 195 return global.PathCreativesSupabase.recordWizardStep(getSlug(), step, action, detail); 196 } catch (err) {
197 console.warn('[PathCreativesProject] recordWizardStep failed', err); 198 return null; 199 } 200 } 201 202 async function saveProjectHistory(entry = {}) { 203 const slug = getSlug(); 204 const brief = getApprovedBrief() || {}; 205 const company = entry.company || getCompany() || brief.companyName || slug; 206 const offer = entry.offer || getOffer() || brief.offerName || 'Offer'; 207 const offerSlug = slugify(offer); 208 209 const payload = { 210 displayDate: entry.displayDate || new Date().toISOString().slice(0, 10), 211 sortDate: entry.sortDate || Number(new Date().toISOString().slice(0, 10).replace(/-/g, '')), 212 thumbnails: entry.thumbnails || [], 213 colors: entry.colors || [], 214 logoUrl: entry.logoUrl || null, 215 creatives: entry.creatives || [], 216 creativeCount: entry.creativeCount || (entry.creatives?.length ?? 4), 217 insights: entry.insights ?? 0, 218 publishedAt: entry.publishedAt || new Date().toISOString(), 219 status: entry.status || 'delivered', 220 snapshot: { 221 briefSummary: entry.briefSummary || getBriefOverview(brief), 222 briefStrategy: entry.briefStrategy || brief.brandContext?.strategy || '', 223 sourceUrl: entry.sourceUrl || getUrl() || brief.url, 224 company, 225 offer, 226 offerSlug, 227 mode: getProjectMode(), 228 ...(entry.snapshot || {}) 229 } 230 }; 231 232 if (global.PathCreativesHistory?.upsertLocalProject) { 233 try { 234 global.PathCreativesHistory.upsertLocalProject({ 235 slug, 236 company, 237 offer, 238 offerSlug, 239 ...payload, 240 sourceUrl: payload.snapshot.sourceUrl, 241 briefSummary: payload.snapshot.briefSummary, 242 briefStrategy: payload.snapshot.briefStrategy 243 }); 244 } catch (err) { 245 console.warn('[PathCreativesProject] local history save failed', err); 246 } 247 } 248 249 if (global.PathCreativesSupabase) { 250 try { 251 await global.PathCreativesSupabase.init(); 252 await global.PathCreativesSupabase.saveHistory(slug, payload); 253 await global.PathCreativesSupabase.upsertProject({ 254 slug, 255 name: company, 256 company, 257 offer, 258 offerSlug, 259 sourceUrl: payload.snapshot.sourceUrl, 260 status: payload.status, 261 briefSummary: payload.snapshot.briefSummary, 262 briefStrategy: payload.snapshot.briefStrategy, 263 creativeCount: payload.creativeCount, 264 insights: payload.insights 265 }); 266 } catch (err) { 267 console.warn('[PathCreativesProject] saveProjectHistory failed', err); 268 return false; 269 } 270 } 271 272 return true; 273 } 274 275 async function ensureProjectRow(meta = {}) { 276 const slug = getSlug(); 277 const company = meta.companyName || getCompany() || slug; 278 const offer = meta.offerName || getOffer() || 'Offer'; 279 const payload = { 280 slug, 281 name: company, 282 company, 283 offer, 284 offerSlug: slugify(offer), 285 sourceUrl: meta.url || getUrl(), 286 status: meta.status || 'draft' 287 }; 288 289 if (!global.PathCreativesSupabase) return payload; 290 try { 291 await global.PathCreativesSupabase.init(); 292 await global.PathCreativesSupabase.upsertProject(payload); 293 if (meta.url || getUrl()) { 294 await recordWizardStep(1, 'url_entered', { url: payload.sourceUrl }); 295 } 296 } catch (err) { 297 console.warn('[PathCreativesProject] ensureProjectRow failed', err); 298 } 299 return payload; 300 } 301 302 async function saveBrandKit(scrape) { 303 const kit = brandKitFromScrape(scrape); 304 if (!kit) return false; 305 setScrape(scrape); 306 await ensureProjectRow({ 307 companyName: getCompany(), 308 url: scrape.url || getUrl() 309 }); 310 311 if (!global.PathCreativesSupabase) return false; 312 try { 313 await global.PathCreativesSupabase.init(); 314 return global.PathCreativesSupabase.saveBrandKit(getSlug(), kit); 315 } catch (err) { 316 console.warn('[PathCreativesProject] saveBrandKit failed', err); 317 return false; 318 } 319 } 320 321 async function persistDraftBrief(brief) { 322 const payload = stripBriefForStorage(brief); 323 if (!payload) return false;
324 setDraftBrief(payload); 325 sessionStorage.setItem(KEYS.briefingSeen, '1'); 326 327 await ensureProjectRow({ 328 companyName: payload.companyName || getCompany(), 329 offerName: payload.offerName || getOffer(), 330 url: payload.url || getUrl() 331 }); 332 333 if (!global.PathCreativesSupabase) return false; 334 try { 335 await global.PathCreativesSupabase.init(); 336 return global.PathCreativesSupabase.upsertProject({ 337 slug: getSlug(), 338 name: payload.companyName || getCompany() || getSlug(), 339 company: payload.companyName || getCompany(), 340 offer: payload.offerName || getOffer(), 341 offerSlug: slugify(payload.offerName || getOffer()), 342 sourceUrl: payload.url || getUrl(), 343 status: 'draft', 344 brief: payload, 345 briefApproved: false, 346 briefSummary: payload.overview || null, 347 briefStrategy: payload.brandContext?.strategy || null 348 }); 349 } catch (err) { 350 console.warn('[PathCreativesProject] persistDraftBrief failed', err); 351 return false; 352 } 353 } 354 355 async function persistApprovedBrief(brief) { 356 const payload = stripBriefForStorage(brief); 357 if (!payload) return false; 358 setApprovedBrief(payload); 359 sessionStorage.setItem(KEYS.briefingSeen, '1'); 360 361 await ensureProjectRow({ 362 companyName: payload.companyName || getCompany(), 363 offerName: payload.offerName || getOffer(), 364 url: payload.url || getUrl(), 365 status: 'brief-approved' 366 }); 367 368 if (!global.PathCreativesSupabase) return false; 369 try { 370 await global.PathCreativesSupabase.init(); 371 const ok = await global.PathCreativesSupabase.upsertProject({ 372 slug: getSlug(), 373 name: payload.companyName || getCompany() || getSlug(), 374 company: payload.companyName || getCompany(), 375 offer: payload.offerName || getOffer(), 376 offerSlug: slugify(payload.offerName || getOffer()), 377 sourceUrl: payload.url || getUrl(), 378 status: 'brief-approved', 379 brief: payload, 380 briefApproved: true, 381 briefSummary: payload.overview || null, 382 briefStrategy: payload.brandContext?.strategy || null 383 }); 384 if (ok) await recordWizardStep(1, 'step_approved'); 385 return ok; 386 } catch (err) { 387 console.warn('[PathCreativesProject] persistApprovedBrief failed', err); 388 return false; 389 } 390 } 391 392 async function hydrateFromSupabase() { 393 if (!global.PathCreativesSupabase) return null; 394 try { 395 await global.PathCreativesSupabase.init(); 396 const slug = getSlug(); 397 const [project, design] = await Promise.all([ 398 global.PathCreativesSupabase.loadProject(slug), 399 global.PathCreativesSupabase.loadDesign(slug) 400 ]); 401 402 if (project?.sourceUrl) setUrl(project.sourceUrl); 403 if (project?.company) setCompany(project.company); 404 if (project?.offer) setOffer(project.offer); 405 if (project?.slug) setSlug(project.slug); 406 407 if (project?.briefApproved && project.brief) { 408 setApprovedBrief({ ...project.brief, url: project.sourceUrl || project.brief.url }); 409 sessionStorage.setItem(KEYS.briefingSeen, '1'); 410 } else if (project?.brief) { 411 setDraftBrief({ ...project.brief, url: project.sourceUrl || project.brief.url }); 412 sessionStorage.setItem(KEYS.briefingSeen, '1'); 413 } 414 415 if (design) { 416 if (design.assetsApproved) markAssetsApprovedSession(); 417 if (design.imageryApproved) markImageryApprovedSession(); 418 419 if (global.PathCreativesDesign && (design.assetsApproved || design.imageryApproved)) { 420 try { 421 const local = global.PathCreativesDesign.load(); 422 if ( 423 (design.assetsApproved && !local.assetsApproved) 424 || (design.imageryApproved && !local.imageryApproved) 425 ) { 426 global.PathCreativesDesign.save({ 427 ...local, 428 assetsApproved: Boolean(design.assetsApproved || local.assetsApproved), 429 imageryApproved: Boolean(design.imageryApproved || local.imageryApproved) 430 }); 431 } 432 } catch (_) { 433 /* ignore */ 434 } 435 } 436 437 const localScrape = getScrape() || {}; 438 const mergedScrape = { 439 ...localScrape, 440 url: design.scrapeMeta?.url || localScrape.url || getUrl(), 441 title: design.scrapeMeta?.title || localScrape.title || '', 442 description: design.scrapeMeta?.description || localScrape.description || '', 443 manual: design.scrapeMeta?.manual ?? localScrape.manual, 444 scrapedAt: design.scrapeMeta?.scrapedAt || localScrape.scrapedAt, 445 colors: (() => { 446 const sessionColors = getKitColorsSession(); 447 if (sessionColors?.length && !global.PathCreativesDesign?.isPlaceholderColors?.(sessionColors)) { 448 return sessionColors; 449 } 450 if (global.PathCreativesDesign?.isPlaceholderColors?.(design.colors) && localScrape.colors?.length) { 451 return localScrape.colors; 452 } 453 return pickAssetArray(design.colors, localScrape.colors); 454 })(), 455 logos: pickAssetArray(design.logos, localScrape.logos), 456 imagery: pickAssetArray(design.pageImagery, localScrape.imagery), 457 screenshot: localScrape.screenshot || null 458 }; 459 setScrape(mergedScrape); 460 } 461 462 return { project, design }; 463 } catch (err) { 464 console.warn('[PathCreativesProject] hydrateFromSupabase failed', err); 465 return null; 466 } 467 } 468 469 function requireUrl(fallback) { 470 const url = getUrl(); 471 if (url) return url; 472 if (fallback) { 473 setUrl(fallback); 474 return fallback; 475 } 476 return ''; 477 } 478 479 function clearSlugScopedWizardState(explicitSlug, explicitOffer) { 480 const slug = explicitSlug || getSlug(); 481 const offer = explicitOffer || getOffer(); 482 try { 483 clearAssetsApprovedSession(); 484 clearImageryApprovedSession(); 485 sessionStorage.removeItem(`pc:kit-colors:${slug}`); 486 localStorage.removeItem(`pc:design:${slug}`); 487 localStorage.removeItem(`pc:generate:${slug}-${slugify(offer || 'offer')}`); 488 } catch (_) { 489 /* ignore */ 490 } 491 } 492 493 function clearAllLocalProjectCaches() { 494 try {
495 const keysToRemove = []; 496 for (let i = 0; i < localStorage.length; i += 1) { 497 const key = localStorage.key(i); 498 if (key && (key.startsWith('pc:design:') || key.startsWith('pc:generate:'))) { 499 keysToRemove.push(key); 500 } 501 } 502 keysToRemove.forEach((key) => localStorage.removeItem(key)); 503 } catch (_) { 504 /* ignore */ 505 } 506 } 507 508 function clearWizard() { 509 const slug = getSlug(); 510 const offer = getOffer(); 511 Object.values(KEYS).forEach((key) => sessionStorage.removeItem(key)); 512 clearSlugScopedWizardState(slug, offer); 513 } 514 515 function resetProjectForNewAnalysis() { 516 const preservedUrl = getUrl(); 517 clearAllLocalProjectCaches(); 518 clearWizard(); 519 if (preservedUrl) setUrl(preservedUrl); 520 if (global.PathCreativesDesign?.clear) { 521 global.PathCreativesDesign.clear(); 522 } 523 } 524 525 function resolveProjectUrl() { 526 const direct = getUrl(); 527 if (direct) return direct; 528 529 const scrapeUrl = getScrape()?.url; 530 if (scrapeUrl) { 531 setUrl(scrapeUrl); 532 return scrapeUrl; 533 } 534 535 const approvedUrl = getApprovedBrief()?.url; 536 if (approvedUrl) { 537 setUrl(approvedUrl); 538 return approvedUrl; 539 } 540 541 const draftUrl = getDraftBrief()?.url; 542 if (draftUrl) { 543 setUrl(draftUrl); 544 return draftUrl; 545 } 546 547 return ''; 548 } 549 550 function designStorageKey() { 551 return `pc:design:${getSlug()}`; 552 } 553 554 function generateStorageKey() { 555 const slug = getSlug(); 556 const offer = slugify(getOffer() || 'offer'); 557 return `pc:generate:${slug}-${offer}`; 558 } 559 560 function imageryApprovedSessionKey() { 561 return `pc:imagery-approved:${getSlug()}`; 562 } 563 564 function assetsApprovedSessionKey() { 565 return `pc:assets-approved:${getSlug()}`; 566 } 567 568 function kitColorsSessionKey() { 569 return `pc:kit-colors:${getSlug()}`; 570 } 571 572 function setKitColorsSession(colors) { 573 if (!Array.isArray(colors) || !colors.length) return; 574 try { 575 sessionStorage.setItem(kitColorsSessionKey(), JSON.stringify(colors)); 576 } catch (_) { 577 /* ignore */ 578 } 579 } 580 581 function getKitColorsSession() { 582 try { 583 const raw = sessionStorage.getItem(kitColorsSessionKey()); 584 if (!raw) return null; 585 const parsed = JSON.parse(raw); 586 return Array.isArray(parsed) ? parsed : null; 587 } catch (_) { 588 return null; 589 } 590 } 591 592 function markAssetsApprovedSession() { 593 try { 594 sessionStorage.setItem(KEYS.assetsApprovedGlobal, '1'); 595 sessionStorage.setItem(assetsApprovedSessionKey(), '1'); 596 } catch (_) { 597 /* ignore */ 598 } 599 } 600 601 function readLocalDesignApproval() { 602 try { 603 const raw = localStorage.getItem(designStorageKey()); 604 if (!raw) return { assetsApproved: false, imageryApproved: false }; 605 const data = JSON.parse(raw); 606 return { 607 assetsApproved: Boolean(data.assetsApproved), 608 imageryApproved: Boolean(data.imageryApproved) 609 }; 610 } catch (_) { 611 return { assetsApproved: false, imageryApproved: false }; 612 } 613 } 614 615 function isAssetsApprovedSession() { 616 try { 617 return sessionStorage.getItem(KEYS.assetsApprovedGlobal) === '1' 618 || sessionStorage.getItem(assetsApprovedSessionKey()) === '1'; 619 } catch (_) { 620 return false; 621 } 622 } 623 624 function isAssetsApproved() { 625 const persisted = readLocalDesignApproval(); 626 return isAssetsApprovedSession() || persisted.assetsApproved; 627 } 628 629 function clearAssetsApprovedSession() { 630 try { 631 sessionStorage.removeItem(KEYS.assetsApprovedGlobal); 632 sessionStorage.removeItem(assetsApprovedSessionKey()); 633 } catch (_) { 634 /* ignore */ 635 } 636 } 637 638 function markImageryApprovedSession() { 639 try { 640 sessionStorage.setItem(KEYS.imageryApprovedGlobal, '1'); 641 sessionStorage.setItem(imageryApprovedSessionKey(), '1'); 642 } catch (_) { 643 /* ignore */ 644 } 645 } 646 647 function isImageryApprovedSession() { 648 try { 649 return sessionStorage.getItem(KEYS.imageryApprovedGlobal) === '1' 650 || sessionStorage.getItem(imageryApprovedSessionKey()) === '1'; 651 } catch (_) { 652 return false; 653 } 654 } 655 656 function isImageryApproved() { 657 const persisted = readLocalDesignApproval(); 658 return isImageryApprovedSession() || persisted.imageryApproved; 659 } 660 661 function clearImageryApprovedSession() { 662 try { 663 sessionStorage.removeItem(KEYS.imageryApprovedGlobal); 664 sessionStorage.removeItem(imageryApprovedSessionKey()); 665 } catch (_) { 666 /* ignore */ 667 } 668 } 669 670 function clearGenerateState() { 671 try { 672 localStorage.removeItem(generateStorageKey()); 673 } catch (_) { 674 /* ignore */ 675 } 676 sessionStorage.removeItem(KEYS.generateSeen); 677 /* Assets / imagery session flags are intentionally kept so re-entering Compile does not bounce. */ 678 } 679 680 function applyWizardSessionFlags(state) { 681 const merged = { ...(state || {}) }; 682 if (!merged.assetsApproved && isAssetsApproved()) { 683 merged.assetsApproved = true; 684 } 685 if (!merged.imageryApproved && isImageryApproved()) { 686 merged.imageryApproved = true; 687 } 688 const sessionColors = getKitColorsSession(); 689 if ( 690 sessionColors?.length 691 && global.PathCreativesDesign?.isPlaceholderColors?.(merged.colors) 692 ) { 693 merged.colors = sessionColors; 694 } 695 return merged; 696 } 697 698 function canAccessAssetsStep() { 699 const brief = getApprovedBrief(); 700 return Boolean(isBriefApproved() && brief && getBriefAds(brief).length); 701 } 702 703 function canAccessDesignStep() { 704 return Boolean(canAccessAssetsStep() && isAssetsApproved()); 705 } 706 707 function canAccessGenerateStep() { 708 return Boolean(canAccessDesignStep() && isImageryApproved()); 709 } 710 711 function getBriefAds(brief) { 712 if (!brief) return []; 713 if (Array.isArray(brief.ads) && brief.ads.length) return brief.ads; 714 if (brief.option?.ads?.length) return brief.option.ads; 715 return []; 716 } 717 718 function getBriefOverview(brief) { 719 if (!brief) return ''; 720 return brief.overview || brief.option?.overview || ''; 721 } 722 723 global.PathCreativesProject = { 724 KEYS, 725 slugify, 726 getUrl, 727 setUrl, 728 resolveProjectUrl, 729 getScrape, 730 setScrape, 731 scrapeMetaFromScrape, 732 brandKitFromScrape, 733 getCompany, 734 setCompany, 735 getOffer, 736 setOffer, 737 getSlug, 738 setSlug, 739 getDraftBrief, 740 setDraftBrief, 741 isBriefApproved, 742 getApprovedBrief, 743 setApprovedBrief, 744 getBriefAds, 745 canAccessAssetsStep, 746 canAccessDesignStep, 747 canAccessGenerateStep, 748 getBriefOverview, 749 syncMetaFromBrief, 750 stripBriefForStorage, 751 ensureProjectRow, 752 saveBrandKit, 753 persistDraftBrief, 754 persistApprovedBrief, 755 hydrateFromSupabase, 756 requireUrl, 757 clearWizard, 758 clearAllLocalProjectCaches, 759 resetProjectForNewAnalysis, 760 clearSlugScopedWizardState, 761 designStorageKey, 762 generateStorageKey, 763 clearGenerateState, 764 applyWizardSessionFlags, 765 readLocalDesignApproval, 766 markAssetsApprovedSession, 767 isAssetsApprovedSession, 768 isAssetsApproved, 769 clearAssetsApprovedSession, 770 setKitColorsSession, 771 getKitColorsSession, 772 markImageryApprovedSession, 773 isImageryApprovedSession, 774 isImageryApproved, 775 clearImageryApprovedSession, 776 getProjectMode, 777 setProjectMode, 778 isDemoMode, 779 getFlowMode, 780 setFlowMode, 781 isConsolidatedFlow, 782 recordWizardStep, 783 saveProjectHistory 784 }; 785})(typeof window !== 'undefined' ? window : globalThis);
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.