your-ai-workflow-firebase-os 1.2.58 → 1.2.59

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/vite.cjs.js CHANGED
@@ -57,7 +57,7 @@ export default function App() {
57
57
  // registerMicrocomponent('pricing', PricingContent);
58
58
  import { registerMicrocomponent } from 'your-ai-workflow-firebase-os';
59
59
  export { registerMicrocomponent };
60
- `,s=`${r}
60
+ `,s=`@source "../../node_modules/your-ai-workflow-firebase-os/dist";`,c=e=>{if(e.includes(`@source`)&&e.includes(`your-ai-workflow-firebase-os`))return e;let t=e.match(/@import\s+(['"])tailwindcss\1[^;\n]*;/);if(!t||t.index===void 0)return e;let n=t.index+t[0].length;return`${e.slice(0,n)}\n${s}${e.slice(n)}`},l=`${r}
61
61
  import { StrictMode } from 'react'
62
62
  import { createRoot } from 'react-dom/client'
63
63
 
@@ -81,7 +81,7 @@ createRoot(document.getElementById('root')!).render(
81
81
  <App />
82
82
  </StrictMode>,
83
83
  )
84
- `,c=`src/your-ai-workflow-firebase-os`,l=`${c}/configs`,u=`${c}/microcomponents`,d=`${c}/Home.tsx`,f=[`theme.config.ts`,`contactForm.config.ts`,`supportForm.config.ts`,`registry.ts`,`pagesRegistry.ts`,`home.config.ts`],p=e=>e.split(n.sep).join(`/`),m=e=>n.basename(e).replace(`.config.ts`,``).replace(/Tab$/,``).toLowerCase(),h=e=>n.basename(e).replace(`.config.ts`,``).replace(/Page$/,``).toLowerCase(),g=e=>n.basename(e).replace(`.config.ts`,``).replace(/Form$/,``).toLowerCase(),_=e=>{let t=n.basename(e).replace(/\.tsx$/,``).replace(/Content$/,``).replace(/([a-z0-9])([A-Z])/g,`$1_$2`).toLowerCase();return[t,t.replace(/_/g,``)]},v=e=>n.basename(e).replace(/\.config\.ts$/,``);function y(e){let t=n.basename(e);return f.includes(t)?`shipped`:/Tab\.config\.ts$/.test(t)?`tab`:/Page\.config\.ts$/.test(t)?`page`:/Form\.config\.ts$/.test(t)?`form`:`other`}var b=e=>t.existsSync(e)?t.readdirSync(e,{withFileTypes:!0}).filter(e=>e.isFile()).map(e=>e.name).sort():[],x=e=>t.existsSync(n.join(e,c));function S(e,t){let r=p(n.relative(e,t));return r.startsWith(l+`/`)||r.startsWith(u+`/`)||r===d}function C(e){let r=[],i=n.join(e,l);for(let e of b(i)){if(!/\.tsx?$/.test(e))continue;let t=n.join(i,e),a=`${l}/${e}`,o=y(e);o===`tab`?r.push({kind:o,file:a,abs:t,name:e,id:m(e)}):o===`page`?r.push({kind:o,file:a,abs:t,name:e,id:h(e)}):o===`form`||/Form\.config\.ts$/.test(e)?r.push({kind:`form`,file:a,abs:t,name:e,id:g(e)}):r.push({kind:`other`,file:a,abs:t,name:e,id:``})}let a=n.join(e,u);for(let e of b(a))e.endsWith(`.tsx`)&&e.replace(/\.tsx$/,``)!==`registry`&&r.push({kind:`microcomponent`,file:`${u}/${e}`,abs:n.join(a,e),name:e,id:_(e)[0]});let o=n.join(e,d);return t.existsSync(o)&&r.push({kind:`other`,file:d,abs:o,name:`Home.tsx`,id:``}),r}var w=e=>e.filter(e=>e.kind!==`other`),T=[{test:e=>/Content\.tsx$/.test(e),home:u},{test:e=>/(Tab|Page|Form)\.config\.tsx?$/.test(e),home:l}],E=new Set([`node_modules`,`.git`,`dist`,`build`]);function D(e){let r=[],i=n.join(e,`src`),a=(i,o)=>{if(!(o>6||!t.existsSync(i)))for(let s of t.readdirSync(i,{withFileTypes:!0})){let t=n.join(i,s.name),c=p(n.relative(e,t));if(s.isDirectory()){E.has(s.name)||a(t,o+1);continue}if(!(c===`${l}/${s.name}`||c===`${u}/${s.name}`)){for(let{test:e,home:t}of T)if(e(s.name)){r.push(`${c}: files like this are only found directly in ${t}/ — move it to ${t}/${s.name}.`);break}}}};return a(i,0),r.sort()}var O=e=>{let t=n.basename(e);return T.some(e=>e.test(t))},k=e=>JSON.stringify(e);function A(e){let t=[],n=[],r=[],i=[];return w(e).forEach((e,a)=>{let o=`m${a}`;if(t.push(`import * as ${o} from ${k(p(e.abs))};`),e.kind===`tab`)n.push(`{
84
+ `,u=`src/your-ai-workflow-firebase-os`,d=`${u}/configs`,f=`${u}/microcomponents`,p=`${u}/Home.tsx`,m=[`theme.config.ts`,`contactForm.config.ts`,`supportForm.config.ts`,`registry.ts`,`pagesRegistry.ts`,`home.config.ts`],h=e=>e.split(n.sep).join(`/`),g=e=>n.basename(e).replace(`.config.ts`,``).replace(/Tab$/,``).toLowerCase(),_=e=>n.basename(e).replace(`.config.ts`,``).replace(/Page$/,``).toLowerCase(),v=e=>n.basename(e).replace(`.config.ts`,``).replace(/Form$/,``).toLowerCase(),y=e=>{let t=n.basename(e).replace(/\.tsx$/,``).replace(/Content$/,``).replace(/([a-z0-9])([A-Z])/g,`$1_$2`).toLowerCase();return[t,t.replace(/_/g,``)]},b=e=>n.basename(e).replace(/\.config\.ts$/,``);function x(e){let t=n.basename(e);return m.includes(t)?`shipped`:/Tab\.config\.ts$/.test(t)?`tab`:/Page\.config\.ts$/.test(t)?`page`:/Form\.config\.ts$/.test(t)?`form`:`other`}var S=e=>t.existsSync(e)?t.readdirSync(e,{withFileTypes:!0}).filter(e=>e.isFile()).map(e=>e.name).sort():[],C=e=>t.existsSync(n.join(e,u));function w(e,t){let r=h(n.relative(e,t));return r.startsWith(d+`/`)||r.startsWith(f+`/`)||r===p}function T(e){let r=[],i=n.join(e,d);for(let e of S(i)){if(!/\.tsx?$/.test(e))continue;let t=n.join(i,e),a=`${d}/${e}`,o=x(e);o===`tab`?r.push({kind:o,file:a,abs:t,name:e,id:g(e)}):o===`page`?r.push({kind:o,file:a,abs:t,name:e,id:_(e)}):o===`form`||/Form\.config\.ts$/.test(e)?r.push({kind:`form`,file:a,abs:t,name:e,id:v(e)}):r.push({kind:`other`,file:a,abs:t,name:e,id:``})}let a=n.join(e,f);for(let e of S(a))e.endsWith(`.tsx`)&&e.replace(/\.tsx$/,``)!==`registry`&&r.push({kind:`microcomponent`,file:`${f}/${e}`,abs:n.join(a,e),name:e,id:y(e)[0]});let o=n.join(e,p);return t.existsSync(o)&&r.push({kind:`other`,file:p,abs:o,name:`Home.tsx`,id:``}),r}var E=e=>e.filter(e=>e.kind!==`other`),D=[{test:e=>/Content\.tsx$/.test(e),home:f},{test:e=>/(Tab|Page|Form)\.config\.tsx?$/.test(e),home:d}],ee=new Set([`node_modules`,`.git`,`dist`,`build`]);function O(e){let r=[],i=n.join(e,`src`),a=(i,o)=>{if(!(o>6||!t.existsSync(i)))for(let s of t.readdirSync(i,{withFileTypes:!0})){let t=n.join(i,s.name),c=h(n.relative(e,t));if(s.isDirectory()){ee.has(s.name)||a(t,o+1);continue}if(!(c===`${d}/${s.name}`||c===`${f}/${s.name}`)){for(let{test:e,home:t}of D)if(e(s.name)){r.push(`${c}: files like this are only found directly in ${t}/ — move it to ${t}/${s.name}.`);break}}}};return a(i,0),r.sort()}var te=e=>{let t=n.basename(e);return D.some(e=>e.test(t))},k=e=>JSON.stringify(e);function A(e){let t=[],n=[],r=[],i=[];return E(e).forEach((e,a)=>{let o=`m${a}`;if(t.push(`import * as ${o} from ${k(h(e.abs))};`),e.kind===`tab`)n.push(`{
85
85
  const configVal = pickConfig(${o});
86
86
  if (configVal) {
87
87
  const tabId = ${k(e.id)};
@@ -127,13 +127,13 @@ createRoot(document.getElementById('root')!).render(
127
127
  forms[finalConfig.systemName] = finalConfig;
128
128
  forms[finalConfig.systemName.toLowerCase()] = finalConfig;
129
129
  }
130
- }`);else if(e.kind===`microcomponent`){let[t,n]=_(e.name);i.push(`if (${o} && ${o}.default) {
130
+ }`);else if(e.kind===`microcomponent`){let[t,n]=y(e.name);i.push(`if (${o} && ${o}.default) {
131
131
  microcomponents[${k(t)}] = ${o}.default;
132
132
  // Also answer to the squashed form ("mobile_apps" -> "mobileapps"): a page id
133
133
  // derived from a file name (mobileAppsPage.config.ts -> "mobileapps") has no
134
134
  // underscores, and the page must still find MobileAppsContent.tsx.
135
135
  microcomponents[${k(n)}] = ${o}.default;
136
- }`)}}),`// Generated by Your AI Firebase from the files in ${c}/.
136
+ }`)}}),`// Generated by Your AI Firebase from the files in ${u}/.
137
137
  // Never written to disk; never edit. Add or remove a file and it updates itself.
138
138
  ${t.join(`
139
139
  `)}
@@ -173,4 +173,4 @@ if (typeof window !== 'undefined') {
173
173
  export const pagesRegistry = pages;
174
174
  export const formsRegistry = forms;
175
175
  export const microcomponentRegistry = microcomponents;
176
- `}var j=e=>A(C(e)),M=e=>e.split(`/`).pop()||e,N=e=>/\/configs\/[^/]+$/.test(e),P=e=>/\/microcomponents\/[^/]+\.tsx$/.test(e),F=e=>e.replace(/\/\*[\s\S]*?\*\//g,``).replace(/(^|[^:\\'"`])\/\/.*$/gm,`$1`),I=(e,t)=>{let n=RegExp(`(?:^|[\\s,{])${t}\\s*:\\s*([^,\\n}]+)`,`g`),r=[],i;for(;i=n.exec(e);)r.push(i[1].trim());return r},L=e=>{let t=e.match(/^(['"`])([^'"`]*)\1$/);return t?t[2]:null},R=e=>/export\s+(?:const|let|var|function|class)\s+\w*Config\b/.test(e)||/export\s*\{[^}]*\b\w*Config\b[^}]*\}/.test(e),z=e=>/export\s+default\b/.test(e),B=e=>e.toLowerCase().replace(/[^a-z0-9]+/g,`_`).replace(/^_|_$/g,``),V=[`board`,`table`,`calendar`,`drive`],H={board:{call:`actions.openInlineEditor(categoryId)`,token:`openInlineEditor`},table:{call:`actions.addRow()`,token:`addRow`},calendar:{call:`actions.openAddModal()`,token:`openAddModal`},drive:{call:`the openUploadModal() prop`,token:`openUploadModal`}},ee=/(?:copy (?:this|the sentence|it) (?:in)?to|paste (?:this|it) (?:in)?to|copy for (?:your |the )?(?:ai|assistant)|ask your assistant|your assistant.{0,3}s chat|copy this to change)[^\n]{0,40}/i,te=e=>{let t=I(e,`route`);return{first:t[0],count:t.length}};function U(e){let t=[],n=[],r=new Map,i=new Set;for(let{file:t}of e)if(P(t))for(let e of _(M(t)))i.add(e);let a=new Map;for(let{file:t,source:n}of e){if(!N(t))continue;let e=M(t),r=y(e);if(r!==`tab`&&r!==`page`)continue;let i=F(n),o=I(i,`template`)[0],s=o===void 0?null:L(o);if(!s||!V.includes(s))continue;let c=I(i,r===`tab`?`tabName`:`pageName`)[0],l=c===void 0?null:L(c),u=[r===`tab`?m(e):h(e),...l?[B(l),B(l).replace(/_/g,``)]:[]];for(let e of u)a.set(e,s)}for(let{file:o,source:s}of e){let e=M(o),c=F(s);if(N(o)){let n=y(e);if(n===`shipped`)continue;if(n===`other`){t.push(`${o}: rename it to somethingTab.config.ts (a dashboard page), somethingPage.config.ts (a page for anyone) or somethingForm.config.ts (a form).`);continue}if(!R(c)&&!z(c)&&t.push(`${o}: export one const named ${v(e)}Config.`),n===`tab`||n===`page`){let{first:a,count:s}=te(c),l=a===void 0?null:L(a);if(s===0||l===null||!l.startsWith(`/`))t.push(`${o}: route must be one string starting with /, e.g. route: '/onboarding'. A page has exactly one web address.`);else{let e=r.get(l);e?t.push(`${o}: route '${l}' is already used by ${e}. One page, one file, one address.`):r.set(l,o)}let u=I(c,`template`)[0],d=u===void 0?null:L(u);if(!d||![`board`,`table`,`calendar`,`drive`,`inline_form`,`popup_form`].includes(d)){let r=n===`tab`?m(e):h(e),a=I(c,n===`tab`?`tabName`:`pageName`)[0],s=a===void 0?null:L(a),l=e=>e.toLowerCase().replace(/[^a-z0-9]+/g,`_`).replace(/^_|_$/g,``);if(![r,...s?[l(s),l(s).replace(/_/g,``)]:[]].some(e=>i.has(e))){let n=v(e).replace(/(Tab|Page)$/,``),r=(s||n).split(/[^a-zA-Z0-9]+/).filter(Boolean).map(e=>e.charAt(0).toUpperCase()+e.slice(1)).join(``);t.push(`${o}: no content file matches this page, so it renders empty — create src/your-ai-workflow-firebase-os/microcomponents/${r}Content.tsx with export default.`)}}}if(n===`tab`){let e=I(c,`pageType`)[0],n=e===void 0?null:L(e);n!==`user`&&n!==`internal`&&n!==`admin`&&t.push(`${o}: pageType must be 'user', 'internal' or 'admin'. A dashboard page is never public.`)}if(n===`form`){let n=g(e),r=I(c,`systemName`)[0];r!==void 0&&L(r)!==n&&t.push(`${o}: systemName must be '${n}'.`)}continue}if(P(o)){z(c)||t.push(`${o}: add export default.`);let r=_(e).map(e=>a.get(e)).find(Boolean),i=/<form\b/.test(c)||/\bonSubmit\b/.test(c);if(i&&r){let e=H[r];c.includes(e.token)||n.push(`${o}: this page is a ${r} and already has its own add and edit dialog, themed for light and dark — open it with ${e.call} instead of building a form here.`)}else i&&!/ContactPopup/.test(c)&&n.push(`${o}: forms are *Form.config.ts files shown with <ContactPopup formId="…" />; a hand-written form never reaches the Inbox.`);let s=c.match(/['"`](?:mem|user|admin|pub)_notifications['"`]/);s&&t.push(`${o}: ${s[0]} is the bell's own collection and pages never touch it — delete that code and send the notification with notify({ toUid | toRole, title, body, link }) imported from 'your-ai-workflow-firebase-os'.`),/<style[\s>]/.test(c)&&t.push(`${o}: a <style> tag is global CSS and restyles every page of the app — delete it and style with theme classes (bg-background, text-foreground, glass-panel, btn-primary).`);let l=c.match(/(?<![\w-])(?:h|max-h)-(?:screen|dvh|svh|lvh|\[(?:100|9\d|8\d)(?:vh|dvh|svh|lvh)\])/);l&&n.push(`${o}: ${l[0]} makes the page exactly one screen tall and strands everything below it on a phone — the page is already inside a panel that scrolls, so let it be as long as it needs and drop the height and any overflow-hidden around it.`);let u=c.match(ee);u&&n.push(`${o}: "${u[0]}" is wording off the owner's admin screens — the page is for its visitors, not for the owner or for you. Delete it and any prompt or copy-this box on the page.`);let d=c.match(/\b(?:bg-(?:white|black)|(?:bg|text|border)-(?:gray|slate|zinc|neutral|stone)-\d+)\b/);d&&n.push(`${o}: ${d[0]} is a hard-coded colour that ignores the owner's theme and stays wrong in light or dark mode — use bg-background, text-foreground, border-[var(--panel-border)], glass-panel or btn-primary.`);continue}if(o===d){/<style[\s>]/.test(c)&&t.push(`${o}: a <style> tag is global CSS — while the home page is open it restyles every page of the app, the titles and the menu included — delete it and style with theme classes (bg-background, text-foreground, glass-panel, btn-primary).`);let e=c.match(/(?<![\w-])(?:h|max-h)-(?:screen|dvh|svh|lvh|\[(?:100|9\d|8\d)(?:vh|dvh|svh|lvh)\])/);e&&n.push(`${o}: ${e[0]} makes the page exactly one screen tall and strands everything below it on a phone — let the page be as long as it needs and drop the height and any overflow-hidden around it.`);let r=c.match(/\b(?:bg-(?:white|black)|(?:bg|text|border)-(?:gray|slate|zinc|neutral|stone)-\d+)\b/);r&&n.push(`${o}: ${r[0]} is a hard-coded colour that ignores the owner's theme and stays wrong in light or dark mode — use bg-background, text-foreground, border-[var(--panel-border)], glass-panel or btn-primary.`)}}return{errors:t,warnings:n}}var ne=[`--bg-color`,`--bg-secondary-color`,`--fg-color`,`--accent-color`,`--panel-bg`,`--panel-border`,`--button-bg`,`--button-fg`,`--button-hover-bg`,`--btn-sec-bg`,`--btn-sec-border`,`--btn-sec-text`,`--text-gradient`,`--glow-color`,`--notification-icon-color`,`--selection-bg`,`--selection-text`],re=(e,t)=>{let n=[],r=t.replace(/\/\*[\s\S]*?\*\//g,``);for(let[,t,i]of r.matchAll(/([^{}]+)\{([^{}]*)\}/g)){let r=t.trim(),a=ne.filter(e=>RegExp(`${e}\\s*:[^;}]*!important`).test(i));if(a.length){n.push(`${e}: \`${a[0]}\` is set with \`!important\` here, and the app writes that colour itself from the owner's theme — forcing it freezes it, so their Theme screen stops working. Delete the \`!important\` and set the colour in configs/theme.config.ts instead, in background, backgroundSecondary, foreground, accent or buttonText; every other colour is derived from those five.`);continue}let o=/(?:^|[\s;])(?:font-size|line-height|font)\s*:/.test(i);if(/\.page-title\b/.test(r)){n.push(`${e}: the rule for \`${r.replace(/\s+/g,` `)}\` styles the app's page title, which is the library's chrome and not this file's to restyle — delete the rule; a page heading you want to size is one you write yourself inside a content file.`);continue}let s=i.match(/(?:^|[\s;])font-family\s*:\s*([^;}]+)/);if(s&&!/^\s*['"]?Inter['"]?\s*(?:,|$)/i.test(s[1])){n.push(`${e}: the rule for \`${r.replace(/\s+/g,` `)}\` sets a \`font-family\`, and the app has one typeface — Inter, everywhere, with no setting for it. Delete the \`font-family\` line; the rest of the rule can stay.`);continue}let c=r.split(`,`).map(e=>e.trim()).filter(e=>/^h[1-3]$/.test(e));c.length&&o&&n.push(`${e}: this rule sets a size on \`${c.join(`, `)}\`, which changes every page title in the app — delete the size (font-weight here is fine) and size the headings you write inside your own content file instead.`)}return n};function W(e,t){return U(e.map(e=>({file:e.file,source:t(e.abs)})))}var G=i,K=`\0`+G,q=[{rel:`src/App.tsx`,source:a},{rel:`src/main.tsx`,source:s}],J=e=>`${e} is owned by Your AI Firebase; your edit was discarded.`,ie=e=>e.replace(/[?#].*$/,``),Y=e=>n.normalize(e).split(n.sep).join(`/`),X={info:e=>console.log(e),warn:e=>console.warn(e),error:e=>console.error(e)};function Z(e,r=X){let i=[];for(let{rel:a,source:o}of q){let s=n.join(e,a),c=null;try{c=t.readFileSync(s,`utf8`)}catch{c=null}c!==o&&(t.mkdirSync(n.dirname(s),{recursive:!0}),t.writeFileSync(s,o),i.push(a),c!==null&&r.warn(J(a)))}return i}function Q(e){let r=n.join(e,u,`registry.ts`);return t.existsSync(r)?!1:(t.mkdirSync(n.dirname(r),{recursive:!0}),t.writeFileSync(r,o),!0)}var $=(e,t)=>{let r=Y(ie(t));for(let{rel:t,source:i}of q)if(r===Y(n.join(e,t)))return i;return null};function ae(){let e=process.cwd(),r=!1,i=`build`,a=X,o=()=>{let r=W(C(e),e=>t.readFileSync(e,`utf8`)),i=n.join(e,`src`,`your-ai-workflow-firebase-os`,`theme.css`),a=t.existsSync(i)?re(`src/your-ai-workflow-firebase-os/theme.css`,t.readFileSync(i,`utf8`)):[];return{...r,errors:[...r.errors,...D(e),...a]}};return{name:`your-ai-firebase`,enforce:`pre`,config:()=>({resolve:{dedupe:[`firebase`,`react`,`react-dom`,`react-router-dom`]}}),configResolved(t){e=t.root,i=t.command,a=t.logger,r=x(e),r&&(Z(e,a),Q(e))},resolveId(e){return e===G?K:null},buildStart(){if(!r)return;let{errors:e,warnings:t}=o();for(let e of t)this.warn(e);if(e.length!==0)if(i===`build`)this.error(e[0]);else for(let t of e)a.error(t)},load(t){if(!r)return null;if(t===K){let t=C(e),{errors:n}=o();return n.length&&this.error(n[0]),A(t)}return $(e,t)},configureServer(t){if(!r)return;let i=()=>{let e=t.moduleGraph.getModuleById(K);e&&t.moduleGraph.invalidateModule(e),t.ws.send({type:`full-reload`,path:`*`})},a=r=>{let a=Y(r);if($(e,a)!==null){Z(e,t.config.logger);return}if(!S(e,a)&&!O(a))return;n.relative(e,a).split(n.sep).join(`/`)!==d&&i();let{errors:s,warnings:c}=o();for(let e of c)t.config.logger.warn(e);for(let e of s)t.config.logger.error(e)};t.watcher.on(`add`,a),t.watcher.on(`unlink`,a),t.watcher.on(`change`,a)}}}exports.CANONICAL_APP_TSX=a,exports.CANONICAL_MAIN_TSX=s,exports.CONFIGS_DIR=l,exports.CONSUMER_DIR=c,exports.CONSUMER_REGISTRY_TS=o,exports.MICROCOMPONENTS_DIR=u,exports.OWNED_BANNER=r,exports.RESOLVED_VIRTUAL_ID=K,exports.SHIPPED_CONFIG_FILES=f,exports.VIRTUAL_ID=G,exports.VIRTUAL_REGISTRIES_ID=i,exports.classifyConfigFile=y,exports.discover=C,exports.ensureConsumerRegistry=Q,exports.fileIdFromConfigFile=v,exports.formIdFromFile=g,exports.isConsumerRoot=x,exports.isDiscoveredPath=S,exports.microcomponentKeysFromFile=_,exports.ownedMessage=J,exports.pageIdFromFile=h,exports.registrable=w,exports.restoreOwnedFiles=Z,exports.stripComments=F,exports.tabIdFromFile=m,exports.validate=U,exports.validateDiscovered=W,exports.virtualModuleSource=j,exports.virtualModuleSourceFor=A,exports.yourAiFirebase=ae;
176
+ `}var j=e=>A(T(e)),M=e=>e.split(`/`).pop()||e,N=e=>/\/configs\/[^/]+$/.test(e),P=e=>/\/microcomponents\/[^/]+\.tsx$/.test(e),F=e=>e.replace(/\/\*[\s\S]*?\*\//g,``).replace(/(^|[^:\\'"`])\/\/.*$/gm,`$1`),I=(e,t)=>{let n=RegExp(`(?:^|[\\s,{])${t}\\s*:\\s*([^,\\n}]+)`,`g`),r=[],i;for(;i=n.exec(e);)r.push(i[1].trim());return r},L=e=>{let t=e.match(/^(['"`])([^'"`]*)\1$/);return t?t[2]:null},ne=e=>/export\s+(?:const|let|var|function|class)\s+\w*Config\b/.test(e)||/export\s*\{[^}]*\b\w*Config\b[^}]*\}/.test(e),R=e=>/export\s+default\b/.test(e),z=e=>e.toLowerCase().replace(/[^a-z0-9]+/g,`_`).replace(/^_|_$/g,``),B=[`board`,`table`,`calendar`,`drive`],re={board:{call:`actions.openInlineEditor(categoryId)`,token:`openInlineEditor`},table:{call:`actions.addRow()`,token:`addRow`},calendar:{call:`actions.openAddModal()`,token:`openAddModal`},drive:{call:`the openUploadModal() prop`,token:`openUploadModal`}},ie=/(?:copy (?:this|the sentence|it) (?:in)?to|paste (?:this|it) (?:in)?to|copy for (?:your |the )?(?:ai|assistant)|ask your assistant|your assistant.{0,3}s chat|copy this to change)[^\n]{0,40}/i,ae=e=>{let t=I(e,`route`);return{first:t[0],count:t.length}};function V(e){let t=[],n=[],r=new Map,i=new Set;for(let{file:t}of e)if(P(t))for(let e of y(M(t)))i.add(e);let a=new Map;for(let{file:t,source:n}of e){if(!N(t))continue;let e=M(t),r=x(e);if(r!==`tab`&&r!==`page`)continue;let i=F(n),o=I(i,`template`)[0],s=o===void 0?null:L(o);if(!s||!B.includes(s))continue;let c=I(i,r===`tab`?`tabName`:`pageName`)[0],l=c===void 0?null:L(c),u=[r===`tab`?g(e):_(e),...l?[z(l),z(l).replace(/_/g,``)]:[]];for(let e of u)a.set(e,s)}for(let{file:o,source:s}of e){let e=M(o),c=F(s);if(N(o)){let n=x(e);if(n===`shipped`)continue;if(n===`other`){t.push(`${o}: rename it to somethingTab.config.ts (a dashboard page), somethingPage.config.ts (a page for anyone) or somethingForm.config.ts (a form).`);continue}if(!ne(c)&&!R(c)&&t.push(`${o}: export one const named ${b(e)}Config.`),n===`tab`||n===`page`){let{first:a,count:s}=ae(c),l=a===void 0?null:L(a);if(s===0||l===null||!l.startsWith(`/`))t.push(`${o}: route must be one string starting with /, e.g. route: '/onboarding'. A page has exactly one web address.`);else{let e=r.get(l);e?t.push(`${o}: route '${l}' is already used by ${e}. One page, one file, one address.`):r.set(l,o)}let u=I(c,`template`)[0],d=u===void 0?null:L(u);if(!d||![`board`,`table`,`calendar`,`drive`,`inline_form`,`popup_form`].includes(d)){let r=n===`tab`?g(e):_(e),a=I(c,n===`tab`?`tabName`:`pageName`)[0],s=a===void 0?null:L(a),l=e=>e.toLowerCase().replace(/[^a-z0-9]+/g,`_`).replace(/^_|_$/g,``);if(![r,...s?[l(s),l(s).replace(/_/g,``)]:[]].some(e=>i.has(e))){let n=b(e).replace(/(Tab|Page)$/,``),r=(s||n).split(/[^a-zA-Z0-9]+/).filter(Boolean).map(e=>e.charAt(0).toUpperCase()+e.slice(1)).join(``);t.push(`${o}: no content file matches this page, so it renders empty — create src/your-ai-workflow-firebase-os/microcomponents/${r}Content.tsx with export default.`)}}}if(n===`tab`){let e=I(c,`pageType`)[0],n=e===void 0?null:L(e);n!==`user`&&n!==`internal`&&n!==`admin`&&t.push(`${o}: pageType must be 'user', 'internal' or 'admin'. A dashboard page is never public.`)}if(n===`form`){let n=v(e),r=I(c,`systemName`)[0];r!==void 0&&L(r)!==n&&t.push(`${o}: systemName must be '${n}'.`)}continue}if(P(o)){R(c)||t.push(`${o}: add export default.`);let r=y(e).map(e=>a.get(e)).find(Boolean),i=/<form\b/.test(c)||/\bonSubmit\b/.test(c);if(i&&r){let e=re[r];c.includes(e.token)||n.push(`${o}: this page is a ${r} and already has its own add and edit dialog, themed for light and dark — open it with ${e.call} instead of building a form here.`)}else i&&!/ContactPopup/.test(c)&&n.push(`${o}: forms are *Form.config.ts files shown with <ContactPopup formId="…" />; a hand-written form never reaches the Inbox.`);let s=c.match(/['"`](?:mem|user|admin|pub)_notifications['"`]/);s&&t.push(`${o}: ${s[0]} is the bell's own collection and pages never touch it — delete that code and send the notification with notify({ toUid | toRole, title, body, link }) imported from 'your-ai-workflow-firebase-os'.`),/<style[\s>]/.test(c)&&t.push(`${o}: a <style> tag is global CSS and restyles every page of the app — delete it and style with theme classes (bg-background, text-foreground, glass-panel, btn-primary).`);let l=c.match(/(?<![\w-])(?:h|max-h)-(?:screen|dvh|svh|lvh|\[(?:100|9\d|8\d)(?:vh|dvh|svh|lvh)\])/);l&&n.push(`${o}: ${l[0]} makes the page exactly one screen tall and strands everything below it on a phone — the page is already inside a panel that scrolls, so let it be as long as it needs and drop the height and any overflow-hidden around it.`);let u=c.match(ie);u&&n.push(`${o}: "${u[0]}" is wording off the owner's admin screens — the page is for its visitors, not for the owner or for you. Delete it and any prompt or copy-this box on the page.`);let d=c.match(/\b(?:bg-(?:white|black)|(?:bg|text|border)-(?:gray|slate|zinc|neutral|stone)-\d+)\b/);d&&n.push(`${o}: ${d[0]} is a hard-coded colour that ignores the owner's theme and stays wrong in light or dark mode — use bg-background, text-foreground, border-[var(--panel-border)], glass-panel or btn-primary.`);continue}if(o===p){/<style[\s>]/.test(c)&&t.push(`${o}: a <style> tag is global CSS — while the home page is open it restyles every page of the app, the titles and the menu included — delete it and style with theme classes (bg-background, text-foreground, glass-panel, btn-primary).`);let e=c.match(/(?<![\w-])(?:h|max-h)-(?:screen|dvh|svh|lvh|\[(?:100|9\d|8\d)(?:vh|dvh|svh|lvh)\])/);e&&n.push(`${o}: ${e[0]} makes the page exactly one screen tall and strands everything below it on a phone — let the page be as long as it needs and drop the height and any overflow-hidden around it.`);let r=c.match(/\b(?:bg-(?:white|black)|(?:bg|text|border)-(?:gray|slate|zinc|neutral|stone)-\d+)\b/);r&&n.push(`${o}: ${r[0]} is a hard-coded colour that ignores the owner's theme and stays wrong in light or dark mode — use bg-background, text-foreground, border-[var(--panel-border)], glass-panel or btn-primary.`)}}return{errors:t,warnings:n}}var oe=[`--bg-color`,`--bg-secondary-color`,`--fg-color`,`--accent-color`,`--panel-bg`,`--panel-border`,`--button-bg`,`--button-fg`,`--button-hover-bg`,`--btn-sec-bg`,`--btn-sec-border`,`--btn-sec-text`,`--text-gradient`,`--glow-color`,`--notification-icon-color`,`--selection-bg`,`--selection-text`],H=(e,t)=>{let n=[],r=t.replace(/\/\*[\s\S]*?\*\//g,``);for(let[,t,i]of r.matchAll(/([^{}]+)\{([^{}]*)\}/g)){let r=t.trim(),a=oe.filter(e=>RegExp(`${e}\\s*:[^;}]*!important`).test(i));if(a.length){n.push(`${e}: \`${a[0]}\` is set with \`!important\` here, and the app writes that colour itself from the owner's theme — forcing it freezes it, so their Theme screen stops working. Delete the \`!important\` and set the colour in configs/theme.config.ts instead, in background, backgroundSecondary, foreground, accent or buttonText; every other colour is derived from those five.`);continue}let o=/(?:^|[\s;])(?:font-size|line-height|font)\s*:/.test(i);if(/\.page-title\b/.test(r)){n.push(`${e}: the rule for \`${r.replace(/\s+/g,` `)}\` styles the app's page title, which is the library's chrome and not this file's to restyle — delete the rule; a page heading you want to size is one you write yourself inside a content file.`);continue}let s=i.match(/(?:^|[\s;])font-family\s*:\s*([^;}]+)/);if(s&&!/^\s*['"]?Inter['"]?\s*(?:,|$)/i.test(s[1])){n.push(`${e}: the rule for \`${r.replace(/\s+/g,` `)}\` sets a \`font-family\`, and the app has one typeface — Inter, everywhere, with no setting for it. Delete the \`font-family\` line; the rest of the rule can stay.`);continue}let c=r.split(`,`).map(e=>e.trim()).filter(e=>/^h[1-3]$/.test(e));c.length&&o&&n.push(`${e}: this rule sets a size on \`${c.join(`, `)}\`, which changes every page title in the app — delete the size (font-weight here is fine) and size the headings you write inside your own content file instead.`)}return n};function U(e,t){return V(e.map(e=>({file:e.file,source:t(e.abs)})))}var W=i,G=`\0`+W,K=[{rel:`src/App.tsx`,source:a},{rel:`src/main.tsx`,source:l}],q=e=>`${e} is owned by Your AI Firebase; your edit was discarded.`,J=e=>e.replace(/[?#].*$/,``),Y=e=>n.normalize(e).split(n.sep).join(`/`),X={info:e=>console.log(e),warn:e=>console.warn(e),error:e=>console.error(e)};function Z(e,r=X){let i=[];for(let{rel:a,source:o}of K){let s=n.join(e,a),c=null;try{c=t.readFileSync(s,`utf8`)}catch{c=null}c!==o&&(t.mkdirSync(n.dirname(s),{recursive:!0}),t.writeFileSync(s,o),i.push(a),c!==null&&r.warn(q(a)))}return i}function Q(e){let r=n.join(e,f,`registry.ts`);return t.existsSync(r)?!1:(t.mkdirSync(n.dirname(r),{recursive:!0}),t.writeFileSync(r,o),!0)}var $=(e,t)=>{let r=Y(J(t));for(let{rel:t,source:i}of K)if(r===Y(n.join(e,t)))return i;return null};function se(){let e=process.cwd(),r=!1,i=`build`,a=X,o=()=>{let r=U(T(e),e=>t.readFileSync(e,`utf8`)),i=n.join(e,`src`,`your-ai-workflow-firebase-os`,`theme.css`),a=t.existsSync(i)?H(`src/your-ai-workflow-firebase-os/theme.css`,t.readFileSync(i,`utf8`)):[];return{...r,errors:[...r.errors,...O(e),...a]}};return{name:`your-ai-firebase`,enforce:`pre`,config:()=>({resolve:{dedupe:[`firebase`,`react`,`react-dom`,`react-router-dom`]}}),configResolved(t){e=t.root,i=t.command,a=t.logger,r=C(e),r&&(Z(e,a),Q(e))},resolveId(e){return e===W?G:null},buildStart(){if(!r)return;let{errors:e,warnings:t}=o();for(let e of t)this.warn(e);if(e.length!==0)if(i===`build`)this.error(e[0]);else for(let t of e)a.error(t)},load(t){if(!r)return null;if(t===G){let t=T(e),{errors:n}=o();return n.length&&this.error(n[0]),A(t)}return $(e,t)},transform(e,t){if(!r||!Y(J(t)).endsWith(`src/your-ai-workflow-firebase-os/theme.css`))return null;let n=c(e);return n===e?null:{code:n,map:null}},configureServer(t){if(!r)return;let i=()=>{let e=t.moduleGraph.getModuleById(G);e&&t.moduleGraph.invalidateModule(e),t.ws.send({type:`full-reload`,path:`*`})},a=r=>{let a=Y(r);if($(e,a)!==null){Z(e,t.config.logger);return}if(!w(e,a)&&!te(a))return;n.relative(e,a).split(n.sep).join(`/`)!==p&&i();let{errors:s,warnings:c}=o();for(let e of c)t.config.logger.warn(e);for(let e of s)t.config.logger.error(e)};t.watcher.on(`add`,a),t.watcher.on(`unlink`,a),t.watcher.on(`change`,a)}}}exports.CANONICAL_APP_TSX=a,exports.CANONICAL_MAIN_TSX=l,exports.CONFIGS_DIR=d,exports.CONSUMER_DIR=u,exports.CONSUMER_REGISTRY_TS=o,exports.MICROCOMPONENTS_DIR=f,exports.OWNED_BANNER=r,exports.RESOLVED_VIRTUAL_ID=G,exports.SHIPPED_CONFIG_FILES=m,exports.VIRTUAL_ID=W,exports.VIRTUAL_REGISTRIES_ID=i,exports.classifyConfigFile=x,exports.discover=T,exports.ensureConsumerRegistry=Q,exports.fileIdFromConfigFile=b,exports.formIdFromFile=v,exports.isConsumerRoot=C,exports.isDiscoveredPath=w,exports.microcomponentKeysFromFile=y,exports.ownedMessage=q,exports.pageIdFromFile=_,exports.registrable=E,exports.restoreOwnedFiles=Z,exports.stripComments=F,exports.tabIdFromFile=g,exports.validate=V,exports.validateDiscovered=U,exports.virtualModuleSource=j,exports.virtualModuleSourceFor=A,exports.yourAiFirebase=se;
package/dist/vite.es.js CHANGED
@@ -49,7 +49,13 @@ export default function App() {
49
49
  />
50
50
  );
51
51
  }
52
- `, a = "// Your AI Firebase — the page-content list.\n// The build finds content files automatically where the Your AI Firebase\n// Vite plugin runs; in a runtime without it (Google AI Studio) the two lines\n// per page below are the ONLY thing that loads a page's content file.\n// Add two lines for every page. Never remove another page's lines.\n//\n// Example, for the page at /pricing filled by PricingContent.tsx:\n// import PricingContent from './PricingContent';\n// registerMicrocomponent('pricing', PricingContent);\nimport { registerMicrocomponent } from 'your-ai-workflow-firebase-os';\nexport { registerMicrocomponent };\n", o = `${n}
52
+ `, a = "// Your AI Firebase — the page-content list.\n// The build finds content files automatically where the Your AI Firebase\n// Vite plugin runs; in a runtime without it (Google AI Studio) the two lines\n// per page below are the ONLY thing that loads a page's content file.\n// Add two lines for every page. Never remove another page's lines.\n//\n// Example, for the page at /pricing filled by PricingContent.tsx:\n// import PricingContent from './PricingContent';\n// registerMicrocomponent('pricing', PricingContent);\nimport { registerMicrocomponent } from 'your-ai-workflow-firebase-os';\nexport { registerMicrocomponent };\n", o = "@source \"../../node_modules/your-ai-workflow-firebase-os/dist\";", s = (e) => {
53
+ if (e.includes("@source") && e.includes("your-ai-workflow-firebase-os")) return e;
54
+ let t = e.match(/@import\s+(['"])tailwindcss\1[^;\n]*;/);
55
+ if (!t || t.index === void 0) return e;
56
+ let n = t.index + t[0].length;
57
+ return `${e.slice(0, n)}\n${o}${e.slice(n)}`;
58
+ }, c = `${n}
53
59
  import { StrictMode } from 'react'
54
60
  import { createRoot } from 'react-dom/client'
55
61
 
@@ -73,49 +79,49 @@ createRoot(document.getElementById('root')!).render(
73
79
  <App />
74
80
  </StrictMode>,
75
81
  )
76
- `, s = "src/your-ai-workflow-firebase-os", c = `${s}/configs`, l = `${s}/microcomponents`, u = `${s}/Home.tsx`, d = [
82
+ `, l = "src/your-ai-workflow-firebase-os", u = `${l}/configs`, d = `${l}/microcomponents`, f = `${l}/Home.tsx`, p = [
77
83
  "theme.config.ts",
78
84
  "contactForm.config.ts",
79
85
  "supportForm.config.ts",
80
86
  "registry.ts",
81
87
  "pagesRegistry.ts",
82
88
  "home.config.ts"
83
- ], f = (e) => e.split(t.sep).join("/"), p = (e) => t.basename(e).replace(".config.ts", "").replace(/Tab$/, "").toLowerCase(), m = (e) => t.basename(e).replace(".config.ts", "").replace(/Page$/, "").toLowerCase(), h = (e) => t.basename(e).replace(".config.ts", "").replace(/Form$/, "").toLowerCase(), g = (e) => {
89
+ ], m = (e) => e.split(t.sep).join("/"), h = (e) => t.basename(e).replace(".config.ts", "").replace(/Tab$/, "").toLowerCase(), g = (e) => t.basename(e).replace(".config.ts", "").replace(/Page$/, "").toLowerCase(), _ = (e) => t.basename(e).replace(".config.ts", "").replace(/Form$/, "").toLowerCase(), v = (e) => {
84
90
  let n = t.basename(e).replace(/\.tsx$/, "").replace(/Content$/, "").replace(/([a-z0-9])([A-Z])/g, "$1_$2").toLowerCase();
85
91
  return [n, n.replace(/_/g, "")];
86
- }, _ = (e) => t.basename(e).replace(/\.config\.ts$/, "");
87
- function v(e) {
92
+ }, y = (e) => t.basename(e).replace(/\.config\.ts$/, "");
93
+ function b(e) {
88
94
  let n = t.basename(e);
89
- return d.includes(n) ? "shipped" : /Tab\.config\.ts$/.test(n) ? "tab" : /Page\.config\.ts$/.test(n) ? "page" : /Form\.config\.ts$/.test(n) ? "form" : "other";
95
+ return p.includes(n) ? "shipped" : /Tab\.config\.ts$/.test(n) ? "tab" : /Page\.config\.ts$/.test(n) ? "page" : /Form\.config\.ts$/.test(n) ? "form" : "other";
90
96
  }
91
- var y = (t) => e.existsSync(t) ? e.readdirSync(t, { withFileTypes: !0 }).filter((e) => e.isFile()).map((e) => e.name).sort() : [], b = (n) => e.existsSync(t.join(n, s));
92
- function x(e, n) {
93
- let r = f(t.relative(e, n));
94
- return r.startsWith(c + "/") || r.startsWith(l + "/") || r === u;
97
+ var x = (t) => e.existsSync(t) ? e.readdirSync(t, { withFileTypes: !0 }).filter((e) => e.isFile()).map((e) => e.name).sort() : [], S = (n) => e.existsSync(t.join(n, l));
98
+ function C(e, n) {
99
+ let r = m(t.relative(e, n));
100
+ return r.startsWith(u + "/") || r.startsWith(d + "/") || r === f;
95
101
  }
96
- function S(n) {
97
- let r = [], i = t.join(n, c);
98
- for (let e of y(i)) {
102
+ function w(n) {
103
+ let r = [], i = t.join(n, u);
104
+ for (let e of x(i)) {
99
105
  if (!/\.tsx?$/.test(e)) continue;
100
- let n = t.join(i, e), a = `${c}/${e}`, o = v(e);
106
+ let n = t.join(i, e), a = `${u}/${e}`, o = b(e);
101
107
  o === "tab" ? r.push({
102
108
  kind: o,
103
109
  file: a,
104
110
  abs: n,
105
111
  name: e,
106
- id: p(e)
112
+ id: h(e)
107
113
  }) : o === "page" ? r.push({
108
114
  kind: o,
109
115
  file: a,
110
116
  abs: n,
111
117
  name: e,
112
- id: m(e)
118
+ id: g(e)
113
119
  }) : o === "form" || /Form\.config\.ts$/.test(e) ? r.push({
114
120
  kind: "form",
115
121
  file: a,
116
122
  abs: n,
117
123
  name: e,
118
- id: h(e)
124
+ id: _(e)
119
125
  }) : r.push({
120
126
  kind: "other",
121
127
  file: a,
@@ -124,46 +130,46 @@ function S(n) {
124
130
  id: ""
125
131
  });
126
132
  }
127
- let a = t.join(n, l);
128
- for (let e of y(a)) e.endsWith(".tsx") && e.replace(/\.tsx$/, "") !== "registry" && r.push({
133
+ let a = t.join(n, d);
134
+ for (let e of x(a)) e.endsWith(".tsx") && e.replace(/\.tsx$/, "") !== "registry" && r.push({
129
135
  kind: "microcomponent",
130
- file: `${l}/${e}`,
136
+ file: `${d}/${e}`,
131
137
  abs: t.join(a, e),
132
138
  name: e,
133
- id: g(e)[0]
139
+ id: v(e)[0]
134
140
  });
135
- let o = t.join(n, u);
141
+ let o = t.join(n, f);
136
142
  return e.existsSync(o) && r.push({
137
143
  kind: "other",
138
- file: u,
144
+ file: f,
139
145
  abs: o,
140
146
  name: "Home.tsx",
141
147
  id: ""
142
148
  }), r;
143
149
  }
144
- var C = (e) => e.filter((e) => e.kind !== "other"), w = [{
150
+ var T = (e) => e.filter((e) => e.kind !== "other"), E = [{
145
151
  test: (e) => /Content\.tsx$/.test(e),
146
- home: l
152
+ home: d
147
153
  }, {
148
154
  test: (e) => /(Tab|Page|Form)\.config\.tsx?$/.test(e),
149
- home: c
150
- }], T = new Set([
155
+ home: u
156
+ }], ee = new Set([
151
157
  "node_modules",
152
158
  ".git",
153
159
  "dist",
154
160
  "build"
155
161
  ]);
156
- function E(n) {
162
+ function te(n) {
157
163
  let r = [], i = t.join(n, "src"), a = (i, o) => {
158
164
  if (!(o > 6 || !e.existsSync(i))) for (let s of e.readdirSync(i, { withFileTypes: !0 })) {
159
- let e = t.join(i, s.name), u = f(t.relative(n, e));
165
+ let e = t.join(i, s.name), c = m(t.relative(n, e));
160
166
  if (s.isDirectory()) {
161
- T.has(s.name) || a(e, o + 1);
167
+ ee.has(s.name) || a(e, o + 1);
162
168
  continue;
163
169
  }
164
- if (!(u === `${c}/${s.name}` || u === `${l}/${s.name}`)) {
165
- for (let { test: e, home: t } of w) if (e(s.name)) {
166
- r.push(`${u}: files like this are only found directly in ${t}/ — move it to ${t}/${s.name}.`);
170
+ if (!(c === `${u}/${s.name}` || c === `${d}/${s.name}`)) {
171
+ for (let { test: e, home: t } of E) if (e(s.name)) {
172
+ r.push(`${c}: files like this are only found directly in ${t}/ — move it to ${t}/${s.name}.`);
167
173
  break;
168
174
  }
169
175
  }
@@ -171,15 +177,15 @@ function E(n) {
171
177
  };
172
178
  return a(i, 0), r.sort();
173
179
  }
174
- var ee = (e) => {
180
+ var ne = (e) => {
175
181
  let n = t.basename(e);
176
- return w.some((e) => e.test(n));
182
+ return E.some((e) => e.test(n));
177
183
  }, D = (e) => JSON.stringify(e);
178
184
  function O(e) {
179
185
  let t = [], n = [], r = [], i = [];
180
- return C(e).forEach((e, a) => {
186
+ return T(e).forEach((e, a) => {
181
187
  let o = `m${a}`;
182
- if (t.push(`import * as ${o} from ${D(f(e.abs))};`), e.kind === "tab") n.push(`{
188
+ if (t.push(`import * as ${o} from ${D(m(e.abs))};`), e.kind === "tab") n.push(`{
183
189
  const configVal = pickConfig(${o});
184
190
  if (configVal) {
185
191
  const tabId = ${D(e.id)};
@@ -229,7 +235,7 @@ function O(e) {
229
235
  }
230
236
  }`);
231
237
  else if (e.kind === "microcomponent") {
232
- let [t, n] = g(e.name);
238
+ let [t, n] = v(e.name);
233
239
  i.push(`if (${o} && ${o}.default) {
234
240
  microcomponents[${D(t)}] = ${o}.default;
235
241
  // Also answer to the squashed form ("mobile_apps" -> "mobileapps"): a page id
@@ -238,7 +244,7 @@ function O(e) {
238
244
  microcomponents[${D(n)}] = ${o}.default;
239
245
  }`);
240
246
  }
241
- }), `// Generated by Your AI Firebase from the files in ${s}/.
247
+ }), `// Generated by Your AI Firebase from the files in ${l}/.
242
248
  // Never written to disk; never edit. Add or remove a file and it updates itself.
243
249
  ${t.join("\n")}
244
250
 
@@ -276,7 +282,7 @@ export const formsRegistry = forms;
276
282
  export const microcomponentRegistry = microcomponents;
277
283
  `;
278
284
  }
279
- var k = (e) => O(S(e)), A = (e) => e.split("/").pop() || e, j = (e) => /\/configs\/[^/]+$/.test(e), M = (e) => /\/microcomponents\/[^/]+\.tsx$/.test(e), N = (e) => e.replace(/\/\*[\s\S]*?\*\//g, "").replace(/(^|[^:\\'"`])\/\/.*$/gm, "$1"), P = (e, t) => {
285
+ var k = (e) => O(w(e)), A = (e) => e.split("/").pop() || e, j = (e) => /\/configs\/[^/]+$/.test(e), M = (e) => /\/microcomponents\/[^/]+\.tsx$/.test(e), N = (e) => e.replace(/\/\*[\s\S]*?\*\//g, "").replace(/(^|[^:\\'"`])\/\/.*$/gm, "$1"), P = (e, t) => {
280
286
  let n = RegExp(`(?:^|[\\s,{])${t}\\s*:\\s*([^,\\n}]+)`, "g"), r = [], i;
281
287
  for (; i = n.exec(e);) r.push(i[1].trim());
282
288
  return r;
@@ -305,7 +311,7 @@ var k = (e) => O(S(e)), A = (e) => e.split("/").pop() || e, j = (e) => /\/config
305
311
  call: "the openUploadModal() prop",
306
312
  token: "openUploadModal"
307
313
  }
308
- }, V = /(?:copy (?:this|the sentence|it) (?:in)?to|paste (?:this|it) (?:in)?to|copy for (?:your |the )?(?:ai|assistant)|ask your assistant|your assistant.{0,3}s chat|copy this to change)[^\n]{0,40}/i, te = (e) => {
314
+ }, V = /(?:copy (?:this|the sentence|it) (?:in)?to|paste (?:this|it) (?:in)?to|copy for (?:your |the )?(?:ai|assistant)|ask your assistant|your assistant.{0,3}s chat|copy this to change)[^\n]{0,40}/i, re = (e) => {
309
315
  let t = P(e, "route");
310
316
  return {
311
317
  first: t[0],
@@ -314,28 +320,28 @@ var k = (e) => O(S(e)), A = (e) => e.split("/").pop() || e, j = (e) => /\/config
314
320
  };
315
321
  function H(e) {
316
322
  let t = [], n = [], r = /* @__PURE__ */ new Map(), i = /* @__PURE__ */ new Set();
317
- for (let { file: t } of e) if (M(t)) for (let e of g(A(t))) i.add(e);
323
+ for (let { file: t } of e) if (M(t)) for (let e of v(A(t))) i.add(e);
318
324
  let a = /* @__PURE__ */ new Map();
319
325
  for (let { file: t, source: n } of e) {
320
326
  if (!j(t)) continue;
321
- let e = A(t), r = v(e);
327
+ let e = A(t), r = b(e);
322
328
  if (r !== "tab" && r !== "page") continue;
323
329
  let i = N(n), o = P(i, "template")[0], s = o === void 0 ? null : F(o);
324
330
  if (!s || !z.includes(s)) continue;
325
- let c = P(i, r === "tab" ? "tabName" : "pageName")[0], l = c === void 0 ? null : F(c), u = [r === "tab" ? p(e) : m(e), ...l ? [R(l), R(l).replace(/_/g, "")] : []];
331
+ let c = P(i, r === "tab" ? "tabName" : "pageName")[0], l = c === void 0 ? null : F(c), u = [r === "tab" ? h(e) : g(e), ...l ? [R(l), R(l).replace(/_/g, "")] : []];
326
332
  for (let e of u) a.set(e, s);
327
333
  }
328
334
  for (let { file: o, source: s } of e) {
329
335
  let e = A(o), c = N(s);
330
336
  if (j(o)) {
331
- let n = v(e);
337
+ let n = b(e);
332
338
  if (n === "shipped") continue;
333
339
  if (n === "other") {
334
340
  t.push(`${o}: rename it to somethingTab.config.ts (a dashboard page), somethingPage.config.ts (a page for anyone) or somethingForm.config.ts (a form).`);
335
341
  continue;
336
342
  }
337
- if (!I(c) && !L(c) && t.push(`${o}: export one const named ${_(e)}Config.`), n === "tab" || n === "page") {
338
- let { first: a, count: s } = te(c), l = a === void 0 ? null : F(a);
343
+ if (!I(c) && !L(c) && t.push(`${o}: export one const named ${y(e)}Config.`), n === "tab" || n === "page") {
344
+ let { first: a, count: s } = re(c), l = a === void 0 ? null : F(a);
339
345
  if (s === 0 || l === null || !l.startsWith("/")) t.push(`${o}: route must be one string starting with /, e.g. route: '/onboarding'. A page has exactly one web address.`);
340
346
  else {
341
347
  let e = r.get(l);
@@ -350,9 +356,9 @@ function H(e) {
350
356
  "inline_form",
351
357
  "popup_form"
352
358
  ].includes(d)) {
353
- let r = n === "tab" ? p(e) : m(e), a = P(c, n === "tab" ? "tabName" : "pageName")[0], s = a === void 0 ? null : F(a), l = (e) => e.toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_|_$/g, "");
359
+ let r = n === "tab" ? h(e) : g(e), a = P(c, n === "tab" ? "tabName" : "pageName")[0], s = a === void 0 ? null : F(a), l = (e) => e.toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_|_$/g, "");
354
360
  if (![r, ...s ? [l(s), l(s).replace(/_/g, "")] : []].some((e) => i.has(e))) {
355
- let n = _(e).replace(/(Tab|Page)$/, ""), r = (s || n).split(/[^a-zA-Z0-9]+/).filter(Boolean).map((e) => e.charAt(0).toUpperCase() + e.slice(1)).join("");
361
+ let n = y(e).replace(/(Tab|Page)$/, ""), r = (s || n).split(/[^a-zA-Z0-9]+/).filter(Boolean).map((e) => e.charAt(0).toUpperCase() + e.slice(1)).join("");
356
362
  t.push(`${o}: no content file matches this page, so it renders empty — create src/your-ai-workflow-firebase-os/microcomponents/${r}Content.tsx with export default.`);
357
363
  }
358
364
  }
@@ -362,14 +368,14 @@ function H(e) {
362
368
  n !== "user" && n !== "internal" && n !== "admin" && t.push(`${o}: pageType must be 'user', 'internal' or 'admin'. A dashboard page is never public.`);
363
369
  }
364
370
  if (n === "form") {
365
- let n = h(e), r = P(c, "systemName")[0];
371
+ let n = _(e), r = P(c, "systemName")[0];
366
372
  r !== void 0 && F(r) !== n && t.push(`${o}: systemName must be '${n}'.`);
367
373
  }
368
374
  continue;
369
375
  }
370
376
  if (M(o)) {
371
377
  L(c) || t.push(`${o}: add export default.`);
372
- let r = g(e).map((e) => a.get(e)).find(Boolean), i = /<form\b/.test(c) || /\bonSubmit\b/.test(c);
378
+ let r = v(e).map((e) => a.get(e)).find(Boolean), i = /<form\b/.test(c) || /\bonSubmit\b/.test(c);
373
379
  if (i && r) {
374
380
  let e = B[r];
375
381
  c.includes(e.token) || n.push(`${o}: this page is a ${r} and already has its own add and edit dialog, themed for light and dark — open it with ${e.call} instead of building a form here.`);
@@ -384,7 +390,7 @@ function H(e) {
384
390
  d && n.push(`${o}: ${d[0]} is a hard-coded colour that ignores the owner's theme and stays wrong in light or dark mode — use bg-background, text-foreground, border-[var(--panel-border)], glass-panel or btn-primary.`);
385
391
  continue;
386
392
  }
387
- if (o === u) {
393
+ if (o === f) {
388
394
  /<style[\s>]/.test(c) && t.push(`${o}: a <style> tag is global CSS — while the home page is open it restyles every page of the app, the titles and the menu included — delete it and style with theme classes (bg-background, text-foreground, glass-panel, btn-primary).`);
389
395
  let e = c.match(/(?<![\w-])(?:h|max-h)-(?:screen|dvh|svh|lvh|\[(?:100|9\d|8\d)(?:vh|dvh|svh|lvh)\])/);
390
396
  e && n.push(`${o}: ${e[0]} makes the page exactly one screen tall and strands everything below it on a phone — let the page be as long as it needs and drop the height and any overflow-hidden around it.`);
@@ -397,7 +403,7 @@ function H(e) {
397
403
  warnings: n
398
404
  };
399
405
  }
400
- var ne = [
406
+ var ie = [
401
407
  "--bg-color",
402
408
  "--bg-secondary-color",
403
409
  "--fg-color",
@@ -415,10 +421,10 @@ var ne = [
415
421
  "--notification-icon-color",
416
422
  "--selection-bg",
417
423
  "--selection-text"
418
- ], re = (e, t) => {
424
+ ], ae = (e, t) => {
419
425
  let n = [], r = t.replace(/\/\*[\s\S]*?\*\//g, "");
420
426
  for (let [, t, i] of r.matchAll(/([^{}]+)\{([^{}]*)\}/g)) {
421
- let r = t.trim(), a = ne.filter((e) => RegExp(`${e}\\s*:[^;}]*!important`).test(i));
427
+ let r = t.trim(), a = ie.filter((e) => RegExp(`${e}\\s*:[^;}]*!important`).test(i));
422
428
  if (a.length) {
423
429
  n.push(`${e}: \`${a[0]}\` is set with \`!important\` here, and the app writes that colour itself from the owner's theme — forcing it freezes it, so their Theme screen stops working. Delete the \`!important\` and set the colour in configs/theme.config.ts instead, in background, backgroundSecondary, foreground, accent or buttonText; every other colour is derived from those five.`);
424
430
  continue;
@@ -451,7 +457,7 @@ var W = r, G = "\0" + W, K = [{
451
457
  source: i
452
458
  }, {
453
459
  rel: "src/main.tsx",
454
- source: o
460
+ source: c
455
461
  }], q = (e) => `${e} is owned by Your AI Firebase; your edit was discarded.`, J = (e) => e.replace(/[?#].*$/, ""), Y = (e) => t.normalize(e).split(t.sep).join("/"), X = {
456
462
  info: (e) => console.log(e),
457
463
  warn: (e) => console.warn(e),
@@ -471,7 +477,7 @@ function Z(n, r = X) {
471
477
  return i;
472
478
  }
473
479
  function Q(n) {
474
- let r = t.join(n, l, "registry.ts");
480
+ let r = t.join(n, d, "registry.ts");
475
481
  return e.existsSync(r) ? !1 : (e.mkdirSync(t.dirname(r), { recursive: !0 }), e.writeFileSync(r, a), !0);
476
482
  }
477
483
  var $ = (e, n) => {
@@ -479,14 +485,14 @@ var $ = (e, n) => {
479
485
  for (let { rel: n, source: i } of K) if (r === Y(t.join(e, n))) return i;
480
486
  return null;
481
487
  };
482
- function ie() {
488
+ function oe() {
483
489
  let n = process.cwd(), r = !1, i = "build", a = X, o = () => {
484
- let r = U(S(n), (t) => e.readFileSync(t, "utf8")), i = t.join(n, "src", "your-ai-workflow-firebase-os", "theme.css"), a = e.existsSync(i) ? re("src/your-ai-workflow-firebase-os/theme.css", e.readFileSync(i, "utf8")) : [];
490
+ let r = U(w(n), (t) => e.readFileSync(t, "utf8")), i = t.join(n, "src", "your-ai-workflow-firebase-os", "theme.css"), a = e.existsSync(i) ? ae("src/your-ai-workflow-firebase-os/theme.css", e.readFileSync(i, "utf8")) : [];
485
491
  return {
486
492
  ...r,
487
493
  errors: [
488
494
  ...r.errors,
489
- ...E(n),
495
+ ...te(n),
490
496
  ...a
491
497
  ]
492
498
  };
@@ -501,7 +507,7 @@ function ie() {
501
507
  "react-router-dom"
502
508
  ] } }),
503
509
  configResolved(e) {
504
- n = e.root, i = e.command, a = e.logger, r = b(n), r && (Z(n, a), Q(n));
510
+ n = e.root, i = e.command, a = e.logger, r = S(n), r && (Z(n, a), Q(n));
505
511
  },
506
512
  resolveId(e) {
507
513
  return e === W ? G : null;
@@ -516,11 +522,19 @@ function ie() {
516
522
  load(e) {
517
523
  if (!r) return null;
518
524
  if (e === G) {
519
- let e = S(n), { errors: t } = o();
525
+ let e = w(n), { errors: t } = o();
520
526
  return t.length && this.error(t[0]), O(e);
521
527
  }
522
528
  return $(n, e);
523
529
  },
530
+ transform(e, t) {
531
+ if (!r || !Y(J(t)).endsWith("src/your-ai-workflow-firebase-os/theme.css")) return null;
532
+ let n = s(e);
533
+ return n === e ? null : {
534
+ code: n,
535
+ map: null
536
+ };
537
+ },
524
538
  configureServer(e) {
525
539
  if (!r) return;
526
540
  let i = () => {
@@ -535,8 +549,8 @@ function ie() {
535
549
  Z(n, e.config.logger);
536
550
  return;
537
551
  }
538
- if (!x(n, a) && !ee(a)) return;
539
- t.relative(n, a).split(t.sep).join("/") !== u && i();
552
+ if (!C(n, a) && !ne(a)) return;
553
+ t.relative(n, a).split(t.sep).join("/") !== f && i();
540
554
  let { errors: s, warnings: c } = o();
541
555
  for (let t of c) e.config.logger.warn(t);
542
556
  for (let t of s) e.config.logger.error(t);
@@ -546,4 +560,4 @@ function ie() {
546
560
  };
547
561
  }
548
562
  //#endregion
549
- export { i as CANONICAL_APP_TSX, o as CANONICAL_MAIN_TSX, c as CONFIGS_DIR, s as CONSUMER_DIR, a as CONSUMER_REGISTRY_TS, l as MICROCOMPONENTS_DIR, n as OWNED_BANNER, G as RESOLVED_VIRTUAL_ID, d as SHIPPED_CONFIG_FILES, W as VIRTUAL_ID, r as VIRTUAL_REGISTRIES_ID, v as classifyConfigFile, S as discover, Q as ensureConsumerRegistry, _ as fileIdFromConfigFile, h as formIdFromFile, b as isConsumerRoot, x as isDiscoveredPath, g as microcomponentKeysFromFile, q as ownedMessage, m as pageIdFromFile, C as registrable, Z as restoreOwnedFiles, N as stripComments, p as tabIdFromFile, H as validate, U as validateDiscovered, k as virtualModuleSource, O as virtualModuleSourceFor, ie as yourAiFirebase };
563
+ export { i as CANONICAL_APP_TSX, c as CANONICAL_MAIN_TSX, u as CONFIGS_DIR, l as CONSUMER_DIR, a as CONSUMER_REGISTRY_TS, d as MICROCOMPONENTS_DIR, n as OWNED_BANNER, G as RESOLVED_VIRTUAL_ID, p as SHIPPED_CONFIG_FILES, W as VIRTUAL_ID, r as VIRTUAL_REGISTRIES_ID, b as classifyConfigFile, w as discover, Q as ensureConsumerRegistry, y as fileIdFromConfigFile, _ as formIdFromFile, S as isConsumerRoot, C as isDiscoveredPath, v as microcomponentKeysFromFile, q as ownedMessage, g as pageIdFromFile, T as registrable, Z as restoreOwnedFiles, N as stripComments, h as tabIdFromFile, H as validate, U as validateDiscovered, k as virtualModuleSource, O as virtualModuleSourceFor, oe as yourAiFirebase };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "your-ai-workflow-firebase-os",
3
- "version": "1.2.58",
3
+ "version": "1.2.59",
4
4
  "description": "Your AI Firebase — a complete Firebase-powered admin app in one React component.",
5
5
  "type": "module",
6
6
  "main": "dist/your-ai-workflow-firebase-os.cjs.js",
@@ -155,18 +155,47 @@ if (!fs.existsSync(fbosDir)) fs.mkdirSync(fbosDir, { recursive: true });
155
155
  // would override the library's own chrome for the whole app.
156
156
  const sourceCssPath = path.join(__dirname, '..', 'src', 'index.css');
157
157
  const themeCssPath = path.join(fbosDir, 'theme.css');
158
+
159
+ // The one line that keeps the project's own Tailwind compile from
160
+ // re-laying-out the library's screens. The sheet compiled from theme.css
161
+ // loads after the library's stylesheet and wins ties in the utilities layer,
162
+ // but Tailwind never scans node_modules — so without this line it holds only
163
+ // the classes the project's files mention, and one content file's
164
+ // `grid-cols-1` beats the chrome's `md:grid-cols-3`: the Users screen
165
+ // stacks, grids widen, the navbar loses its desktop padding, all because one
166
+ // page exists. Keep in step with THEME_SOURCE_LINE in src/vite/canonical.ts.
167
+ const THEME_SOURCE_LINE = '@source "../../node_modules/your-ai-workflow-firebase-os/dist";';
168
+ const withThemeSource = (css) => {
169
+ if (css.includes('@source') && css.includes('your-ai-workflow-firebase-os')) return css;
170
+ const anchor = css.match(/@import\s+(['"])tailwindcss\1[^;\n]*;/);
171
+ if (!anchor || anchor.index === undefined) return css;
172
+ const at = anchor.index + anchor[0].length;
173
+ return `${css.slice(0, at)}\n${THEME_SOURCE_LINE}${css.slice(at)}`;
174
+ };
175
+
158
176
  if (fs.existsSync(sourceCssPath) && !fs.existsSync(themeCssPath)) {
159
177
  const fullCss = fs.readFileSync(sourceCssPath, 'utf8');
160
178
  const marker = fullCss.indexOf('/* ── FBOS:THEME-END');
161
- const themeCss =
179
+ const themeCss = withThemeSource(
162
180
  (marker === -1 ? fullCss : fullCss.slice(0, marker)) +
163
181
  `\n/* ─────────────────────────────────────────────────────────────────────\n` +
164
182
  ` This file is yours. Change the variables above to restyle the app.\n` +
165
183
  ` Component classes (.glass-panel, .text-gradient, .btn-primary, ...)\n` +
166
184
  ` ship compiled inside the library — do not redefine them here.\n` +
167
- ` ───────────────────────────────────────────────────────────────────── */\n`;
185
+ ` ───────────────────────────────────────────────────────────────────── */\n`,
186
+ );
168
187
  fs.writeFileSync(themeCssPath, themeCss);
169
188
  console.log(' ✓ Created theme.css in your src/your-ai-workflow-firebase-os directory');
189
+ } else if (fs.existsSync(themeCssPath)) {
190
+ // theme.css is write-once — it is the owner's styling — but this line is
191
+ // machinery, not styling: a project updated in place keeps its copy from
192
+ // before the line existed, so add it there too.
193
+ const current = fs.readFileSync(themeCssPath, 'utf8');
194
+ const patched = withThemeSource(current);
195
+ if (patched !== current) {
196
+ fs.writeFileSync(themeCssPath, patched);
197
+ console.log(" ✓ Added the @source line to theme.css — it keeps the app's own screens laid out");
198
+ }
170
199
  }
171
200
 
172
201
  // Copy configurations
@@ -586,7 +615,12 @@ everywhere, including the home page.
586
615
  pins the title's size regardless. Never write a \`font-family\` there at
587
616
  all: the app has one typeface, Inter, and the build fails on any rule
588
617
  that changes it. The heading WEIGHT rules already in that file are the
589
- theme's own — leave them as they are.
618
+ theme's own — leave them as they are. And the \`@source\` line at the top
619
+ of that file is machinery, not styling: NEVER delete, move or edit it.
620
+ Without it the app's own screens fall apart the moment a page you write
621
+ uses a class the app also uses — side-by-side layouts stack into one
622
+ column, grids widen, the space under the menu shrinks — on every page at
623
+ once, while your page's own file looks completely innocent.
590
624
 
591
625
  ## Changing the home page
592
626