your-ai-workflow-firebase-os 1.2.55 → 1.2.58
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/index.d.ts +2 -0
- package/dist/vite.cjs.js +8 -8
- package/dist/vite.es.js +129 -113
- package/dist/your-ai-workflow-firebase-os.cjs.js +16 -16
- package/dist/your-ai-workflow-firebase-os.css +1 -1
- package/dist/your-ai-workflow-firebase-os.es.js +972 -894
- package/package.json +1 -1
- package/scripts/postinstall.js +44 -2
package/dist/index.d.ts
CHANGED
|
@@ -6,6 +6,8 @@ export { useConfig } from './lib/ConfigContext';
|
|
|
6
6
|
export type { FirebaseOSConfig } from './lib/ConfigContext';
|
|
7
7
|
export { db, auth, storage } from './lib/firebase';
|
|
8
8
|
export { ContactPopup } from './components/ContactPopup';
|
|
9
|
+
export { notify } from './lib/notify';
|
|
10
|
+
export type { NotifyInput } from './lib/notify';
|
|
9
11
|
export { registerMicrocomponent } from './microcomponents/registry';
|
|
10
12
|
export { Button } from './components/Button';
|
|
11
13
|
export { Card } from './components/Card';
|
package/dist/vite.cjs.js
CHANGED
|
@@ -81,10 +81,10 @@ 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=[`theme.config.ts`,`contactForm.config.ts`,`supportForm.config.ts`,`registry.ts`,`pagesRegistry.ts`,`home.config.ts`],
|
|
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(`{
|
|
85
85
|
const configVal = pickConfig(${o});
|
|
86
86
|
if (configVal) {
|
|
87
|
-
const tabId = ${
|
|
87
|
+
const tabId = ${k(e.id)};
|
|
88
88
|
const finalTab = {
|
|
89
89
|
config: {
|
|
90
90
|
...configVal,
|
|
@@ -102,7 +102,7 @@ createRoot(document.getElementById('root')!).render(
|
|
|
102
102
|
}`);else if(e.kind===`page`)n.push(`{
|
|
103
103
|
const configVal = pickConfig(${o});
|
|
104
104
|
if (configVal) {
|
|
105
|
-
const pageId = ${
|
|
105
|
+
const pageId = ${k(e.id)};
|
|
106
106
|
const finalPage = {
|
|
107
107
|
config: {
|
|
108
108
|
...configVal,
|
|
@@ -120,19 +120,19 @@ createRoot(document.getElementById('root')!).render(
|
|
|
120
120
|
}`);else if(e.kind===`form`)r.push(`{
|
|
121
121
|
const configVal = pickConfig(${o});
|
|
122
122
|
if (configVal) {
|
|
123
|
-
const formId = ${
|
|
123
|
+
const formId = ${k(e.id)};
|
|
124
124
|
// The form's id is its file name; systemName always follows the file.
|
|
125
125
|
const finalConfig = { ...configVal, systemName: formId };
|
|
126
126
|
forms[formId] = finalConfig;
|
|
127
127
|
forms[finalConfig.systemName] = finalConfig;
|
|
128
128
|
forms[finalConfig.systemName.toLowerCase()] = finalConfig;
|
|
129
129
|
}
|
|
130
|
-
}`);else if(e.kind===`microcomponent`){let[t,n]=
|
|
131
|
-
microcomponents[${
|
|
130
|
+
}`);else if(e.kind===`microcomponent`){let[t,n]=_(e.name);i.push(`if (${o} && ${o}.default) {
|
|
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
|
-
microcomponents[${
|
|
135
|
+
microcomponents[${k(n)}] = ${o}.default;
|
|
136
136
|
}`)}}),`// Generated by Your AI Firebase from the files in ${c}/.
|
|
137
137
|
// Never written to disk; never edit. Add or remove a file and it updates itself.
|
|
138
138
|
${t.join(`
|
|
@@ -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 te=e=>O(S(e)),k=e=>e.split(`/`).pop()||e,A=e=>/\/configs\/[^/]+$/.test(e),j=e=>/\/microcomponents\/[^/]+\.tsx$/.test(e),M=e=>e.replace(/\/\*[\s\S]*?\*\//g,``).replace(/(^|[^:\\'"`])\/\/.*$/gm,`$1`),N=(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},P=e=>{let t=e.match(/^(['"`])([^'"`]*)\1$/);return t?t[2]:null},F=e=>/export\s+(?:const|let|var|function|class)\s+\w*Config\b/.test(e)||/export\s*\{[^}]*\b\w*Config\b[^}]*\}/.test(e),I=e=>/export\s+default\b/.test(e),L=e=>e.toLowerCase().replace(/[^a-z0-9]+/g,`_`).replace(/^_|_$/g,``),R=[`board`,`table`,`calendar`,`drive`],z={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`}},B=/(?: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,V=e=>{let t=N(e,`route`);return{first:t[0],count:t.length}};function H(e){let t=[],n=[],r=new Map,i=new Set;for(let{file:t}of e)if(j(t))for(let e of g(k(t)))i.add(e);let a=new Map;for(let{file:t,source:n}of e){if(!A(t))continue;let e=k(t),r=v(e);if(r!==`tab`&&r!==`page`)continue;let i=M(n),o=N(i,`template`)[0],s=o===void 0?null:P(o);if(!s||!R.includes(s))continue;let c=N(i,r===`tab`?`tabName`:`pageName`)[0],l=c===void 0?null:P(c),u=[r===`tab`?p(e):m(e),...l?[L(l),L(l).replace(/_/g,``)]:[]];for(let e of u)a.set(e,s)}for(let{file:o,source:s}of e){let e=k(o),c=M(s);if(A(o)){let n=v(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(!F(c)&&!I(c)&&t.push(`${o}: export one const named ${_(e)}Config.`),n===`tab`||n===`page`){let{first:a,count:s}=V(c),l=a===void 0?null:P(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=N(c,`template`)[0],d=u===void 0?null:P(u);if(!d||![`board`,`table`,`calendar`,`drive`,`inline_form`,`popup_form`].includes(d)){let r=n===`tab`?p(e):m(e),a=N(c,n===`tab`?`tabName`:`pageName`)[0],s=a===void 0?null:P(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=_(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=N(c,`pageType`)[0],n=e===void 0?null:P(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=h(e),r=N(c,`systemName`)[0];r!==void 0&&P(r)!==n&&t.push(`${o}: systemName must be '${n}'.`)}continue}if(j(o)){I(c)||t.push(`${o}: add export default.`);let r=g(e).map(e=>a.get(e)).find(Boolean),i=/<form\b/.test(c)||/\bonSubmit\b/.test(c);if(i&&r){let e=z[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.`);/<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 s=c.match(/(?<![\w-])(?:h|max-h)-(?:screen|dvh|svh|lvh|\[(?:100|9\d|8\d)(?:vh|dvh|svh|lvh)\])/);s&&n.push(`${o}: ${s[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 l=c.match(B);l&&n.push(`${o}: "${l[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 u=c.match(/\b(?:bg-(?:white|black)|(?:bg|text|border)-(?:gray|slate|zinc|neutral|stone)-\d+)\b/);u&&n.push(`${o}: ${u[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 U=[`--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`],ne=(e,t)=>{let n=[],r=t.replace(/\/\*[\s\S]*?\*\//g,``);for(let[,t,i]of r.matchAll(/([^{}]+)\{([^{}]*)\}/g)){let r=t.trim(),a=U.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 H(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.`,re=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(re(t));for(let{rel:t,source:i}of q)if(r===Y(n.join(e,t)))return i;return null};function ie(){let e=process.cwd(),r=!1,i=`build`,a=X,o=()=>{let r=W(S(e),e=>t.readFileSync(e,`utf8`)),i=n.join(e,`src`,`your-ai-workflow-firebase-os`,`theme.css`),a=t.existsSync(i)?ne(`src/your-ai-workflow-firebase-os/theme.css`,t.readFileSync(i,`utf8`)):[];return{...r,errors:[...r.errors,...T(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=b(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=S(e),{errors:n}=o();return n.length&&this.error(n[0]),O(t)}return $(e,t)},configureServer(t){if(!r)return;let n=()=>{let e=t.moduleGraph.getModuleById(K);e&&t.moduleGraph.invalidateModule(e),t.ws.send({type:`full-reload`,path:`*`})},i=r=>{let i=Y(r);if($(e,i)!==null){Z(e,t.config.logger);return}if(!x(e,i)&&!E(i))return;n();let{errors:a,warnings:s}=o();for(let e of s)t.config.logger.warn(e);for(let e of a)t.config.logger.error(e)};t.watcher.on(`add`,i),t.watcher.on(`unlink`,i),t.watcher.on(`change`,i)}}}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=d,exports.VIRTUAL_ID=G,exports.VIRTUAL_REGISTRIES_ID=i,exports.classifyConfigFile=v,exports.discover=S,exports.ensureConsumerRegistry=Q,exports.fileIdFromConfigFile=_,exports.formIdFromFile=h,exports.isConsumerRoot=b,exports.isDiscoveredPath=x,exports.microcomponentKeysFromFile=g,exports.ownedMessage=J,exports.pageIdFromFile=m,exports.registrable=C,exports.restoreOwnedFiles=Z,exports.stripComments=M,exports.tabIdFromFile=p,exports.validate=H,exports.validateDiscovered=W,exports.virtualModuleSource=te,exports.virtualModuleSourceFor=O,exports.yourAiFirebase=ie;
|
|
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;
|
package/dist/vite.es.js
CHANGED
|
@@ -73,89 +73,96 @@ createRoot(document.getElementById('root')!).render(
|
|
|
73
73
|
<App />
|
|
74
74
|
</StrictMode>,
|
|
75
75
|
)
|
|
76
|
-
`, s = "src/your-ai-workflow-firebase-os", c = `${s}/configs`, l = `${s}/microcomponents`, u = [
|
|
76
|
+
`, s = "src/your-ai-workflow-firebase-os", c = `${s}/configs`, l = `${s}/microcomponents`, u = `${s}/Home.tsx`, d = [
|
|
77
77
|
"theme.config.ts",
|
|
78
78
|
"contactForm.config.ts",
|
|
79
79
|
"supportForm.config.ts",
|
|
80
80
|
"registry.ts",
|
|
81
81
|
"pagesRegistry.ts",
|
|
82
82
|
"home.config.ts"
|
|
83
|
-
],
|
|
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) => {
|
|
84
84
|
let n = t.basename(e).replace(/\.tsx$/, "").replace(/Content$/, "").replace(/([a-z0-9])([A-Z])/g, "$1_$2").toLowerCase();
|
|
85
85
|
return [n, n.replace(/_/g, "")];
|
|
86
|
-
},
|
|
87
|
-
function
|
|
86
|
+
}, _ = (e) => t.basename(e).replace(/\.config\.ts$/, "");
|
|
87
|
+
function v(e) {
|
|
88
88
|
let n = t.basename(e);
|
|
89
|
-
return
|
|
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";
|
|
90
90
|
}
|
|
91
|
-
var
|
|
92
|
-
function
|
|
93
|
-
let r =
|
|
94
|
-
return r.startsWith(c + "/") || r.startsWith(l + "/");
|
|
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;
|
|
95
95
|
}
|
|
96
|
-
function
|
|
97
|
-
let
|
|
98
|
-
for (let e of
|
|
96
|
+
function S(n) {
|
|
97
|
+
let r = [], i = t.join(n, c);
|
|
98
|
+
for (let e of y(i)) {
|
|
99
99
|
if (!/\.tsx?$/.test(e)) continue;
|
|
100
|
-
let
|
|
101
|
-
o === "tab" ?
|
|
100
|
+
let n = t.join(i, e), a = `${c}/${e}`, o = v(e);
|
|
101
|
+
o === "tab" ? r.push({
|
|
102
102
|
kind: o,
|
|
103
103
|
file: a,
|
|
104
|
-
abs:
|
|
104
|
+
abs: n,
|
|
105
105
|
name: e,
|
|
106
|
-
id:
|
|
107
|
-
}) : o === "page" ?
|
|
106
|
+
id: p(e)
|
|
107
|
+
}) : o === "page" ? r.push({
|
|
108
108
|
kind: o,
|
|
109
109
|
file: a,
|
|
110
|
-
abs:
|
|
110
|
+
abs: n,
|
|
111
111
|
name: e,
|
|
112
|
-
id:
|
|
113
|
-
}) : o === "form" || /Form\.config\.ts$/.test(e) ?
|
|
112
|
+
id: m(e)
|
|
113
|
+
}) : o === "form" || /Form\.config\.ts$/.test(e) ? r.push({
|
|
114
114
|
kind: "form",
|
|
115
115
|
file: a,
|
|
116
|
-
abs:
|
|
116
|
+
abs: n,
|
|
117
117
|
name: e,
|
|
118
|
-
id:
|
|
119
|
-
}) :
|
|
118
|
+
id: h(e)
|
|
119
|
+
}) : r.push({
|
|
120
120
|
kind: "other",
|
|
121
121
|
file: a,
|
|
122
|
-
abs:
|
|
122
|
+
abs: n,
|
|
123
123
|
name: e,
|
|
124
124
|
id: ""
|
|
125
125
|
});
|
|
126
126
|
}
|
|
127
|
-
let
|
|
128
|
-
for (let e of
|
|
127
|
+
let a = t.join(n, l);
|
|
128
|
+
for (let e of y(a)) e.endsWith(".tsx") && e.replace(/\.tsx$/, "") !== "registry" && r.push({
|
|
129
129
|
kind: "microcomponent",
|
|
130
130
|
file: `${l}/${e}`,
|
|
131
|
-
abs: t.join(
|
|
131
|
+
abs: t.join(a, e),
|
|
132
132
|
name: e,
|
|
133
|
-
id:
|
|
133
|
+
id: g(e)[0]
|
|
134
134
|
});
|
|
135
|
-
|
|
135
|
+
let o = t.join(n, u);
|
|
136
|
+
return e.existsSync(o) && r.push({
|
|
137
|
+
kind: "other",
|
|
138
|
+
file: u,
|
|
139
|
+
abs: o,
|
|
140
|
+
name: "Home.tsx",
|
|
141
|
+
id: ""
|
|
142
|
+
}), r;
|
|
136
143
|
}
|
|
137
|
-
var
|
|
144
|
+
var C = (e) => e.filter((e) => e.kind !== "other"), w = [{
|
|
138
145
|
test: (e) => /Content\.tsx$/.test(e),
|
|
139
146
|
home: l
|
|
140
147
|
}, {
|
|
141
148
|
test: (e) => /(Tab|Page|Form)\.config\.tsx?$/.test(e),
|
|
142
149
|
home: c
|
|
143
|
-
}],
|
|
150
|
+
}], T = new Set([
|
|
144
151
|
"node_modules",
|
|
145
152
|
".git",
|
|
146
153
|
"dist",
|
|
147
154
|
"build"
|
|
148
155
|
]);
|
|
149
|
-
function
|
|
156
|
+
function E(n) {
|
|
150
157
|
let r = [], i = t.join(n, "src"), a = (i, o) => {
|
|
151
158
|
if (!(o > 6 || !e.existsSync(i))) for (let s of e.readdirSync(i, { withFileTypes: !0 })) {
|
|
152
|
-
let e = t.join(i, s.name), u =
|
|
159
|
+
let e = t.join(i, s.name), u = f(t.relative(n, e));
|
|
153
160
|
if (s.isDirectory()) {
|
|
154
|
-
|
|
161
|
+
T.has(s.name) || a(e, o + 1);
|
|
155
162
|
continue;
|
|
156
163
|
}
|
|
157
164
|
if (!(u === `${c}/${s.name}` || u === `${l}/${s.name}`)) {
|
|
158
|
-
for (let { test: e, home: t } of
|
|
165
|
+
for (let { test: e, home: t } of w) if (e(s.name)) {
|
|
159
166
|
r.push(`${u}: files like this are only found directly in ${t}/ — move it to ${t}/${s.name}.`);
|
|
160
167
|
break;
|
|
161
168
|
}
|
|
@@ -164,18 +171,18 @@ function ee(n) {
|
|
|
164
171
|
};
|
|
165
172
|
return a(i, 0), r.sort();
|
|
166
173
|
}
|
|
167
|
-
var
|
|
174
|
+
var ee = (e) => {
|
|
168
175
|
let n = t.basename(e);
|
|
169
|
-
return
|
|
170
|
-
},
|
|
171
|
-
function
|
|
176
|
+
return w.some((e) => e.test(n));
|
|
177
|
+
}, D = (e) => JSON.stringify(e);
|
|
178
|
+
function O(e) {
|
|
172
179
|
let t = [], n = [], r = [], i = [];
|
|
173
|
-
return
|
|
180
|
+
return C(e).forEach((e, a) => {
|
|
174
181
|
let o = `m${a}`;
|
|
175
|
-
if (t.push(`import * as ${o} from ${
|
|
182
|
+
if (t.push(`import * as ${o} from ${D(f(e.abs))};`), e.kind === "tab") n.push(`{
|
|
176
183
|
const configVal = pickConfig(${o});
|
|
177
184
|
if (configVal) {
|
|
178
|
-
const tabId = ${
|
|
185
|
+
const tabId = ${D(e.id)};
|
|
179
186
|
const finalTab = {
|
|
180
187
|
config: {
|
|
181
188
|
...configVal,
|
|
@@ -194,7 +201,7 @@ function D(e) {
|
|
|
194
201
|
else if (e.kind === "page") n.push(`{
|
|
195
202
|
const configVal = pickConfig(${o});
|
|
196
203
|
if (configVal) {
|
|
197
|
-
const pageId = ${
|
|
204
|
+
const pageId = ${D(e.id)};
|
|
198
205
|
const finalPage = {
|
|
199
206
|
config: {
|
|
200
207
|
...configVal,
|
|
@@ -213,7 +220,7 @@ function D(e) {
|
|
|
213
220
|
else if (e.kind === "form") r.push(`{
|
|
214
221
|
const configVal = pickConfig(${o});
|
|
215
222
|
if (configVal) {
|
|
216
|
-
const formId = ${
|
|
223
|
+
const formId = ${D(e.id)};
|
|
217
224
|
// The form's id is its file name; systemName always follows the file.
|
|
218
225
|
const finalConfig = { ...configVal, systemName: formId };
|
|
219
226
|
forms[formId] = finalConfig;
|
|
@@ -222,13 +229,13 @@ function D(e) {
|
|
|
222
229
|
}
|
|
223
230
|
}`);
|
|
224
231
|
else if (e.kind === "microcomponent") {
|
|
225
|
-
let [t, n] =
|
|
232
|
+
let [t, n] = g(e.name);
|
|
226
233
|
i.push(`if (${o} && ${o}.default) {
|
|
227
|
-
microcomponents[${
|
|
234
|
+
microcomponents[${D(t)}] = ${o}.default;
|
|
228
235
|
// Also answer to the squashed form ("mobile_apps" -> "mobileapps"): a page id
|
|
229
236
|
// derived from a file name (mobileAppsPage.config.ts -> "mobileapps") has no
|
|
230
237
|
// underscores, and the page must still find MobileAppsContent.tsx.
|
|
231
|
-
microcomponents[${
|
|
238
|
+
microcomponents[${D(n)}] = ${o}.default;
|
|
232
239
|
}`);
|
|
233
240
|
}
|
|
234
241
|
}), `// Generated by Your AI Firebase from the files in ${s}/.
|
|
@@ -269,19 +276,19 @@ export const formsRegistry = forms;
|
|
|
269
276
|
export const microcomponentRegistry = microcomponents;
|
|
270
277
|
`;
|
|
271
278
|
}
|
|
272
|
-
var
|
|
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) => {
|
|
273
280
|
let n = RegExp(`(?:^|[\\s,{])${t}\\s*:\\s*([^,\\n}]+)`, "g"), r = [], i;
|
|
274
281
|
for (; i = n.exec(e);) r.push(i[1].trim());
|
|
275
282
|
return r;
|
|
276
|
-
},
|
|
283
|
+
}, F = (e) => {
|
|
277
284
|
let t = e.match(/^(['"`])([^'"`]*)\1$/);
|
|
278
285
|
return t ? t[2] : null;
|
|
279
|
-
},
|
|
286
|
+
}, I = (e) => /export\s+(?:const|let|var|function|class)\s+\w*Config\b/.test(e) || /export\s*\{[^}]*\b\w*Config\b[^}]*\}/.test(e), L = (e) => /export\s+default\b/.test(e), R = (e) => e.toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_|_$/g, ""), z = [
|
|
280
287
|
"board",
|
|
281
288
|
"table",
|
|
282
289
|
"calendar",
|
|
283
290
|
"drive"
|
|
284
|
-
],
|
|
291
|
+
], B = {
|
|
285
292
|
board: {
|
|
286
293
|
call: "actions.openInlineEditor(categoryId)",
|
|
287
294
|
token: "openInlineEditor"
|
|
@@ -298,43 +305,43 @@ var te = (e) => D(x(e)), O = (e) => e.split("/").pop() || e, k = (e) => /\/confi
|
|
|
298
305
|
call: "the openUploadModal() prop",
|
|
299
306
|
token: "openUploadModal"
|
|
300
307
|
}
|
|
301
|
-
},
|
|
302
|
-
let t =
|
|
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) => {
|
|
309
|
+
let t = P(e, "route");
|
|
303
310
|
return {
|
|
304
311
|
first: t[0],
|
|
305
312
|
count: t.length
|
|
306
313
|
};
|
|
307
314
|
};
|
|
308
|
-
function
|
|
315
|
+
function H(e) {
|
|
309
316
|
let t = [], n = [], r = /* @__PURE__ */ new Map(), i = /* @__PURE__ */ new Set();
|
|
310
|
-
for (let { file: t } of e) if (
|
|
317
|
+
for (let { file: t } of e) if (M(t)) for (let e of g(A(t))) i.add(e);
|
|
311
318
|
let a = /* @__PURE__ */ new Map();
|
|
312
319
|
for (let { file: t, source: n } of e) {
|
|
313
|
-
if (!
|
|
314
|
-
let e =
|
|
320
|
+
if (!j(t)) continue;
|
|
321
|
+
let e = A(t), r = v(e);
|
|
315
322
|
if (r !== "tab" && r !== "page") continue;
|
|
316
|
-
let i =
|
|
317
|
-
if (!s || !
|
|
318
|
-
let c =
|
|
323
|
+
let i = N(n), o = P(i, "template")[0], s = o === void 0 ? null : F(o);
|
|
324
|
+
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, "")] : []];
|
|
319
326
|
for (let e of u) a.set(e, s);
|
|
320
327
|
}
|
|
321
328
|
for (let { file: o, source: s } of e) {
|
|
322
|
-
let e =
|
|
323
|
-
if (
|
|
324
|
-
let n =
|
|
329
|
+
let e = A(o), c = N(s);
|
|
330
|
+
if (j(o)) {
|
|
331
|
+
let n = v(e);
|
|
325
332
|
if (n === "shipped") continue;
|
|
326
333
|
if (n === "other") {
|
|
327
334
|
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).`);
|
|
328
335
|
continue;
|
|
329
336
|
}
|
|
330
|
-
if (!
|
|
331
|
-
let { first: a, count: s } =
|
|
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);
|
|
332
339
|
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.`);
|
|
333
340
|
else {
|
|
334
341
|
let e = r.get(l);
|
|
335
342
|
e ? t.push(`${o}: route '${l}' is already used by ${e}. One page, one file, one address.`) : r.set(l, o);
|
|
336
343
|
}
|
|
337
|
-
let u =
|
|
344
|
+
let u = P(c, "template")[0], d = u === void 0 ? null : F(u);
|
|
338
345
|
if (!d || ![
|
|
339
346
|
"board",
|
|
340
347
|
"table",
|
|
@@ -343,37 +350,46 @@ function V(e) {
|
|
|
343
350
|
"inline_form",
|
|
344
351
|
"popup_form"
|
|
345
352
|
].includes(d)) {
|
|
346
|
-
let r = n === "tab" ?
|
|
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, "");
|
|
347
354
|
if (![r, ...s ? [l(s), l(s).replace(/_/g, "")] : []].some((e) => i.has(e))) {
|
|
348
|
-
let n =
|
|
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("");
|
|
349
356
|
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.`);
|
|
350
357
|
}
|
|
351
358
|
}
|
|
352
359
|
}
|
|
353
360
|
if (n === "tab") {
|
|
354
|
-
let e =
|
|
361
|
+
let e = P(c, "pageType")[0], n = e === void 0 ? null : F(e);
|
|
355
362
|
n !== "user" && n !== "internal" && n !== "admin" && t.push(`${o}: pageType must be 'user', 'internal' or 'admin'. A dashboard page is never public.`);
|
|
356
363
|
}
|
|
357
364
|
if (n === "form") {
|
|
358
|
-
let n =
|
|
359
|
-
r !== void 0 &&
|
|
365
|
+
let n = h(e), r = P(c, "systemName")[0];
|
|
366
|
+
r !== void 0 && F(r) !== n && t.push(`${o}: systemName must be '${n}'.`);
|
|
360
367
|
}
|
|
361
368
|
continue;
|
|
362
369
|
}
|
|
363
|
-
if (
|
|
364
|
-
|
|
365
|
-
let r =
|
|
370
|
+
if (M(o)) {
|
|
371
|
+
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);
|
|
366
373
|
if (i && r) {
|
|
367
|
-
let e =
|
|
374
|
+
let e = B[r];
|
|
368
375
|
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.`);
|
|
369
376
|
} 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.`);
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
+
let s = c.match(/['"`](?:mem|user|admin|pub)_notifications['"`]/);
|
|
378
|
+
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).`);
|
|
379
|
+
let l = c.match(/(?<![\w-])(?:h|max-h)-(?:screen|dvh|svh|lvh|\[(?:100|9\d|8\d)(?:vh|dvh|svh|lvh)\])/);
|
|
380
|
+
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.`);
|
|
381
|
+
let u = c.match(V);
|
|
382
|
+
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.`);
|
|
383
|
+
let d = c.match(/\b(?:bg-(?:white|black)|(?:bg|text|border)-(?:gray|slate|zinc|neutral|stone)-\d+)\b/);
|
|
384
|
+
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
|
+
continue;
|
|
386
|
+
}
|
|
387
|
+
if (o === u) {
|
|
388
|
+
/<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
|
+
let e = c.match(/(?<![\w-])(?:h|max-h)-(?:screen|dvh|svh|lvh|\[(?:100|9\d|8\d)(?:vh|dvh|svh|lvh)\])/);
|
|
390
|
+
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.`);
|
|
391
|
+
let r = c.match(/\b(?:bg-(?:white|black)|(?:bg|text|border)-(?:gray|slate|zinc|neutral|stone)-\d+)\b/);
|
|
392
|
+
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.`);
|
|
377
393
|
}
|
|
378
394
|
}
|
|
379
395
|
return {
|
|
@@ -381,7 +397,7 @@ function V(e) {
|
|
|
381
397
|
warnings: n
|
|
382
398
|
};
|
|
383
399
|
}
|
|
384
|
-
var
|
|
400
|
+
var ne = [
|
|
385
401
|
"--bg-color",
|
|
386
402
|
"--bg-secondary-color",
|
|
387
403
|
"--fg-color",
|
|
@@ -399,10 +415,10 @@ var H = [
|
|
|
399
415
|
"--notification-icon-color",
|
|
400
416
|
"--selection-bg",
|
|
401
417
|
"--selection-text"
|
|
402
|
-
],
|
|
418
|
+
], re = (e, t) => {
|
|
403
419
|
let n = [], r = t.replace(/\/\*[\s\S]*?\*\//g, "");
|
|
404
420
|
for (let [, t, i] of r.matchAll(/([^{}]+)\{([^{}]*)\}/g)) {
|
|
405
|
-
let r = t.trim(), a =
|
|
421
|
+
let r = t.trim(), a = ne.filter((e) => RegExp(`${e}\\s*:[^;}]*!important`).test(i));
|
|
406
422
|
if (a.length) {
|
|
407
423
|
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.`);
|
|
408
424
|
continue;
|
|
@@ -422,35 +438,35 @@ var H = [
|
|
|
422
438
|
}
|
|
423
439
|
return n;
|
|
424
440
|
};
|
|
425
|
-
function
|
|
426
|
-
return
|
|
441
|
+
function U(e, t) {
|
|
442
|
+
return H(e.map((e) => ({
|
|
427
443
|
file: e.file,
|
|
428
444
|
source: t(e.abs)
|
|
429
445
|
})));
|
|
430
446
|
}
|
|
431
447
|
//#endregion
|
|
432
448
|
//#region src/vite/plugin.ts
|
|
433
|
-
var
|
|
449
|
+
var W = r, G = "\0" + W, K = [{
|
|
434
450
|
rel: "src/App.tsx",
|
|
435
451
|
source: i
|
|
436
452
|
}, {
|
|
437
453
|
rel: "src/main.tsx",
|
|
438
454
|
source: o
|
|
439
|
-
}],
|
|
455
|
+
}], 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 = {
|
|
440
456
|
info: (e) => console.log(e),
|
|
441
457
|
warn: (e) => console.warn(e),
|
|
442
458
|
error: (e) => console.error(e)
|
|
443
459
|
};
|
|
444
460
|
function Z(n, r = X) {
|
|
445
461
|
let i = [];
|
|
446
|
-
for (let { rel: a, source: o } of
|
|
462
|
+
for (let { rel: a, source: o } of K) {
|
|
447
463
|
let s = t.join(n, a), c = null;
|
|
448
464
|
try {
|
|
449
465
|
c = e.readFileSync(s, "utf8");
|
|
450
466
|
} catch {
|
|
451
467
|
c = null;
|
|
452
468
|
}
|
|
453
|
-
c !== o && (e.mkdirSync(t.dirname(s), { recursive: !0 }), e.writeFileSync(s, o), i.push(a), c !== null && r.warn(
|
|
469
|
+
c !== o && (e.mkdirSync(t.dirname(s), { recursive: !0 }), e.writeFileSync(s, o), i.push(a), c !== null && r.warn(q(a)));
|
|
454
470
|
}
|
|
455
471
|
return i;
|
|
456
472
|
}
|
|
@@ -459,18 +475,18 @@ function Q(n) {
|
|
|
459
475
|
return e.existsSync(r) ? !1 : (e.mkdirSync(t.dirname(r), { recursive: !0 }), e.writeFileSync(r, a), !0);
|
|
460
476
|
}
|
|
461
477
|
var $ = (e, n) => {
|
|
462
|
-
let r = Y(
|
|
463
|
-
for (let { rel: n, source: i } of
|
|
478
|
+
let r = Y(J(n));
|
|
479
|
+
for (let { rel: n, source: i } of K) if (r === Y(t.join(e, n))) return i;
|
|
464
480
|
return null;
|
|
465
481
|
};
|
|
466
|
-
function
|
|
482
|
+
function ie() {
|
|
467
483
|
let n = process.cwd(), r = !1, i = "build", a = X, o = () => {
|
|
468
|
-
let r =
|
|
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")) : [];
|
|
469
485
|
return {
|
|
470
486
|
...r,
|
|
471
487
|
errors: [
|
|
472
488
|
...r.errors,
|
|
473
|
-
...
|
|
489
|
+
...E(n),
|
|
474
490
|
...a
|
|
475
491
|
]
|
|
476
492
|
};
|
|
@@ -485,10 +501,10 @@ function re() {
|
|
|
485
501
|
"react-router-dom"
|
|
486
502
|
] } }),
|
|
487
503
|
configResolved(e) {
|
|
488
|
-
n = e.root, i = e.command, a = e.logger, r =
|
|
504
|
+
n = e.root, i = e.command, a = e.logger, r = b(n), r && (Z(n, a), Q(n));
|
|
489
505
|
},
|
|
490
506
|
resolveId(e) {
|
|
491
|
-
return e ===
|
|
507
|
+
return e === W ? G : null;
|
|
492
508
|
},
|
|
493
509
|
buildStart() {
|
|
494
510
|
if (!r) return;
|
|
@@ -499,35 +515,35 @@ function re() {
|
|
|
499
515
|
},
|
|
500
516
|
load(e) {
|
|
501
517
|
if (!r) return null;
|
|
502
|
-
if (e ===
|
|
503
|
-
let e =
|
|
504
|
-
return t.length && this.error(t[0]),
|
|
518
|
+
if (e === G) {
|
|
519
|
+
let e = S(n), { errors: t } = o();
|
|
520
|
+
return t.length && this.error(t[0]), O(e);
|
|
505
521
|
}
|
|
506
522
|
return $(n, e);
|
|
507
523
|
},
|
|
508
524
|
configureServer(e) {
|
|
509
525
|
if (!r) return;
|
|
510
|
-
let
|
|
511
|
-
let t = e.moduleGraph.getModuleById(
|
|
526
|
+
let i = () => {
|
|
527
|
+
let t = e.moduleGraph.getModuleById(G);
|
|
512
528
|
t && e.moduleGraph.invalidateModule(t), e.ws.send({
|
|
513
529
|
type: "full-reload",
|
|
514
530
|
path: "*"
|
|
515
531
|
});
|
|
516
|
-
},
|
|
517
|
-
let
|
|
518
|
-
if ($(n,
|
|
532
|
+
}, a = (r) => {
|
|
533
|
+
let a = Y(r);
|
|
534
|
+
if ($(n, a) !== null) {
|
|
519
535
|
Z(n, e.config.logger);
|
|
520
536
|
return;
|
|
521
537
|
}
|
|
522
|
-
if (!
|
|
523
|
-
t();
|
|
524
|
-
let { errors:
|
|
525
|
-
for (let t of
|
|
526
|
-
for (let t of
|
|
538
|
+
if (!x(n, a) && !ee(a)) return;
|
|
539
|
+
t.relative(n, a).split(t.sep).join("/") !== u && i();
|
|
540
|
+
let { errors: s, warnings: c } = o();
|
|
541
|
+
for (let t of c) e.config.logger.warn(t);
|
|
542
|
+
for (let t of s) e.config.logger.error(t);
|
|
527
543
|
};
|
|
528
|
-
e.watcher.on("add",
|
|
544
|
+
e.watcher.on("add", a), e.watcher.on("unlink", a), e.watcher.on("change", a);
|
|
529
545
|
}
|
|
530
546
|
};
|
|
531
547
|
}
|
|
532
548
|
//#endregion
|
|
533
|
-
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,
|
|
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 };
|