your-ai-workflow-firebase-os 1.2.39 → 1.2.41

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "your-ai-workflow-firebase-os",
3
- "version": "1.2.39",
3
+ "version": "1.2.41",
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",
@@ -68,7 +68,12 @@ const loadCanonical = async () => {
68
68
  try {
69
69
  const mod = await import(pathToFileURL(path.join(__dirname, '..', 'dist', 'vite.es.js')).href);
70
70
  if (typeof mod.CANONICAL_APP_TSX === 'string' && typeof mod.CANONICAL_MAIN_TSX === 'string') {
71
- return { app: mod.CANONICAL_APP_TSX, main: mod.CANONICAL_MAIN_TSX, banner: mod.OWNED_BANNER || FALLBACK_OWNED_BANNER };
71
+ return {
72
+ app: mod.CANONICAL_APP_TSX,
73
+ main: mod.CANONICAL_MAIN_TSX,
74
+ banner: mod.OWNED_BANNER || FALLBACK_OWNED_BANNER,
75
+ registry: typeof mod.CONSUMER_REGISTRY_TS === 'string' ? mod.CONSUMER_REGISTRY_TS : null,
76
+ };
72
77
  }
73
78
  } catch {
74
79
  // dist/vite.es.js missing or not loadable here — fall back to the templates below.
@@ -185,17 +190,30 @@ copyMachinery('forms/registry.ts', 'registry.ts');
185
190
  copyMachinery('pages/registry.ts', 'pagesRegistry.ts');
186
191
  console.log(' ✓ Copied configuration files and registry to your src/your-ai-workflow-firebase-os/configs directory');
187
192
 
188
- // Copy microcomponents auto-registry
189
- const microcomponentsSourceDir = path.join(__dirname, '..', 'src', 'microcomponents');
193
+ // The page-content list: microcomponents/registry.ts. Written ONCE and never
194
+ // overwritten — since 1.2.41 the assistant appends two lines per page to it
195
+ // (the only path from a content file to its page in a runtime without the
196
+ // Vite plugin, like Google AI Studio), so re-copying it on install would
197
+ // silently un-register every page. Projects created before 1.2.41 keep their
198
+ // glob-based copy: under the plugin it is redundant either way.
199
+ const FALLBACK_CONSUMER_REGISTRY_TS = `// Your AI Firebase — the page-content list.
200
+ // The build finds content files automatically where the Your AI Firebase
201
+ // Vite plugin runs; in a runtime without it (Google AI Studio) the two lines
202
+ // per page below are the ONLY thing that loads a page's content file.
203
+ // Add two lines for every page. Never remove another page's lines.
204
+ //
205
+ // Example, for the page at /pricing filled by PricingContent.tsx:
206
+ // import PricingContent from './PricingContent';
207
+ // registerMicrocomponent('pricing', PricingContent);
208
+ import { registerMicrocomponent } from 'your-ai-workflow-firebase-os';
209
+ export { registerMicrocomponent };
210
+ `;
190
211
  const microcomponentsDestDir = path.join(fbosDir, 'microcomponents');
191
- if (fs.existsSync(microcomponentsSourceDir)) {
212
+ {
192
213
  if (!fs.existsSync(microcomponentsDestDir)) fs.mkdirSync(microcomponentsDestDir, { recursive: true });
193
- const files = fs.readdirSync(microcomponentsSourceDir);
194
- for (const file of files) {
195
- // Only copy the registry — user creates their own content files
196
- if (file === 'registry.ts') {
197
- fs.copyFileSync(path.join(microcomponentsSourceDir, file), path.join(microcomponentsDestDir, file));
198
- }
214
+ const registryDest = path.join(microcomponentsDestDir, 'registry.ts');
215
+ if (!fs.existsSync(registryDest)) {
216
+ fs.writeFileSync(registryDest, canonical?.registry || FALLBACK_CONSUMER_REGISTRY_TS);
199
217
  }
200
218
  const microReadme = path.join(microcomponentsDestDir, 'README.md');
201
219
  if (!fs.existsSync(microReadme)) {
@@ -209,6 +227,9 @@ if (fs.existsSync(microcomponentsSourceDir)) {
209
227
  'Requirements:',
210
228
  '- DEFAULT export, or the registry ignores the file.',
211
229
  '- The name must match the tab/page name in PascalCase + `Content`.',
230
+ '- Two lines in `registry.ts` (import + `registerMicrocomponent`) — the',
231
+ ' build also finds the file on its own, but in a runtime without the',
232
+ ' Vite plugin (Google AI Studio) those lines are what loads it.',
212
233
  '- No `<h1>`, no `<DashboardNav />` — the page already renders both.',
213
234
  '- Renders inside an existing panel: no page wrapper, no `min-h-screen`.',
214
235
  '',
@@ -216,7 +237,7 @@ if (fs.existsSync(microcomponentsSourceDir)) {
216
237
  ''
217
238
  ].join('\n'));
218
239
  }
219
- console.log(' ✓ Copied microcomponents auto-registry to your src/your-ai-workflow-firebase-os/microcomponents directory');
240
+ console.log(' ✓ Ensured the page-content list (microcomponents/registry.ts) exists');
220
241
  }
221
242
 
222
243
  // ── 3b. AI instruction files ─────────────────────────────────────────────
@@ -241,8 +262,10 @@ change the owner can use and a change they cannot.
241
262
  1. Every page is ONE file in \`src/your-ai-workflow-firebase-os/configs/\` named
242
263
  \`somethingTab.config.ts\` (signed-in users, the team, or admins) or
243
264
  \`somethingPage.config.ts\` (anyone), plus ONE content file in
244
- \`microcomponents/\`. Nothing else. Never a second config for the same page.
245
- The one exception is the home page: it is \`Home.tsx\` and has no config.
265
+ \`microcomponents/\` and its TWO lines in \`microcomponents/registry.ts\`
266
+ (import + \`registerMicrocomponent\`). Nothing else. Never a second config
267
+ for the same page. The one exception is the home page: it is \`Home.tsx\`
268
+ and has no config.
246
269
  2. "For my team", "team page", "for us", "internal" all mean
247
270
  \`pageType: 'internal'\` in a \`*Tab.config.ts\`. A \`*Tab.config.ts\` is never
248
271
  \`'public'\` — the app will not honour it.
@@ -264,12 +287,12 @@ change the owner can use and a change they cannot.
264
287
  is on adding THINGS, not on doing the asked thing well.
265
288
  7. A page the owner added on their Pages screen ALREADY EXISTS. The sentence
266
289
  they paste says so, names its web address and names its one content file —
267
- write exactly that file and NOTHING else: no config file, no second
268
- address, nothing to Firestore, and never a reply telling them to create or
269
- fix anything. Only a page asked for in the chat, one that exists nowhere
270
- yet, is YOURS to create, and then it is created as FILES — never in
271
- Firestore: its config file and its content file, both named from the
272
- page's name — a page called "Client onboarding" is
290
+ write exactly that file plus its two registry.ts lines and NOTHING else:
291
+ no config file, no second address, nothing to Firestore, and never a reply
292
+ telling them to create or fix anything. Only a page asked for in the chat,
293
+ one that exists nowhere yet, is YOURS to create, and then it is created as
294
+ FILES — never in Firestore: its config file and its content file, both
295
+ named from the page's name — a page called "Client onboarding" is
273
296
  \`clientOnboardingTab.config.ts\` plus \`ClientOnboardingContent.tsx\`.
274
297
  Nothing else registers a page: no registerPages.ts, no customRoutes, no
275
298
  database writes. When a config file already exists for that page or
@@ -310,7 +333,9 @@ stops the build with one sentence that says the fix.
310
333
  preset and no matching content file (the page would render empty — the
311
334
  error names the exact \`*Content.tsx\` to create); a \`<style>\` tag in a
312
335
  content file (global CSS — it restyles and re-pads every page of the app,
313
- style with theme classes instead).
336
+ style with theme classes instead); a \`*Content.tsx\` or \`*.config.ts\`
337
+ saved anywhere except directly in its own folder — a subfolder, another
338
+ directory — is invisible to the app, so the build stops and names the move.
314
339
  - A hand-written form — a \`<form>\` or an \`onSubmit\` in a content file with
315
340
  no \`<ContactPopup />\` — prints a warning, because its entries never reach
316
341
  the Inbox and never show on the Forms screen. So does a hard-coded colour
@@ -397,8 +422,19 @@ export default function ProdTimerContent({ config, actions }: { config: any; act
397
422
  }
398
423
  \`\`\`
399
424
 
400
- The file is auto-discovered by its name and location. There is no registration
401
- step, no import to add, and no web address to wire.
425
+ Then add TWO lines for it to
426
+ \`src/your-ai-workflow-firebase-os/microcomponents/registry.ts\` (create that
427
+ file if it is missing — its first line is
428
+ \`import { registerMicrocomponent } from 'your-ai-workflow-firebase-os';\`):
429
+
430
+ \`\`\`ts
431
+ import ProdTimerContent from './ProdTimerContent';
432
+ registerMicrocomponent('prod_timer', ProdTimerContent);
433
+ \`\`\`
434
+
435
+ The key is the file's name without \`Content\`, lowercased with \`_\` between
436
+ words. Never remove another page's lines from registry.ts. There is no web
437
+ address to wire — the page finds its content by that key.
402
438
 
403
439
  ### The pages that already exist, and the one file each is filled by
404
440
 
@@ -422,7 +458,9 @@ and never a new page, a new config file or a new form.
422
458
  Rules 1-6 apply to microcomponent files only. Rules 7-9 (styling) apply
423
459
  everywhere, including the home page.
424
460
 
425
- 1. Create only that one file. Do NOT edit \`src/App.tsx\` or \`src/main.tsx\`.
461
+ 1. Create only that one file, plus its two lines in
462
+ \`microcomponents/registry.ts\`. Do NOT edit \`src/App.tsx\` or \`src/main.tsx\`,
463
+ and never remove another page's lines from registry.ts.
426
464
  2. Do NOT use the \`customRoutes\` prop and do NOT add react-router routes.
427
465
  3. Use a DEFAULT export. A named-only export is silently ignored by the registry.
428
466
  4. Do NOT render \`<DashboardNav />\`, a navbar, or any menu — the page has one.
@@ -701,20 +739,19 @@ starting layout, its form), then pasted the row's sentence and finished it:
701
739
  > already made and it is already in the menu — do not create a page, a web
702
740
  > address or a settings file for it, and do not edit any other file.
703
741
  > Write one file:
704
- > src/your-ai-workflow-firebase-os/microcomponents/ClientOnboardingContent.tsx,
742
+ > src/your-ai-workflow-firebase-os/microcomponents/OnboardingContent.tsx,
705
743
  > with a default export. That file is what the page shows.
706
- > The page already starts from the board layout — the heading and the saving
707
- > are already built and stay on the page. Your file draws only what goes
708
- > inside the panel.
709
- > Your component receives everything it needs as props: ({ config, actions })
710
- > — actions.tasks and actions.categories hold the saved items;
711
- > actions.saveTask(id, data), actions.deleteTask(id),
712
- > actions.saveCategory(id, data) and actions.deleteCategory(id) change them;
713
- > actions.openInlineEditor(categoryId, item) opens the built-in editor;
714
- > actions.user is the signed-in person, and every item you save must include
715
- > uid: actions.user.uid. Read and change data ONLY through these props — do
716
- > not import firebase and do not write your own loading, saving or uploading
717
- > code.
744
+ > Then add two lines to
745
+ > src/your-ai-workflow-firebase-os/microcomponents/registry.ts so the app
746
+ > loads your file: import OnboardingContent from './OnboardingContent'; and
747
+ > registerMicrocomponent('onboarding', OnboardingContent); —
748
+ > registerMicrocomponent is imported there from 'your-ai-workflow-firebase-os'.
749
+ > Never remove another page's lines from that file.
750
+ > Make it match the rest of my app: style with the app's own theme styles so
751
+ > it looks right in light and dark. Do not pick your own colors.
752
+ > Until your file exists the page shows a ready-made board layout. Treat it
753
+ > as a reference, not a rule — your file replaces everything inside the
754
+ > page, so build the whole thing the way I describe below.
718
755
  > Here is what I would like on it: columns, in this order: To do, In
719
756
  > progress, Done. Each card has a client name (short text), a contact email,
720
757
  > a start date and a short note (long text). Anyone on the team can add a
@@ -724,19 +761,20 @@ starting layout, its form), then pasted the row's sentence and finished it:
724
761
 
725
762
  Every line is a decision already made. The audience, the web address
726
763
  (\`route\`), the name (\`tabName\` / \`pageName\`), the heading (\`tabTitle\` /
727
- \`pageTitle\`) and "already starts from the ... layout" (\`template\`) were set
728
- on the Pages screen — read them from the \`config\` your content file receives,
729
- and never write them again. When the sentence lists props, they are real: the
730
- layout renders your component with exactly those, so a content file that
731
- ignores them and opens its own Firestore connection is wrong even if it runs. THE FILE NAME RULE: the content file is the page's
732
- name in PascalCase plus \`Content.tsx\` (a page called "Client onboarding" ->
733
- \`ClientOnboardingContent.tsx\`); use exactly the name in the request, or the
734
- page never finds your file. "No file uploads" means draw no upload control;
735
- the "button at the top" is yours to draw inside the content; and a question's
736
- kind in brackets — short text, long text, email, phone, number, money, yes-no,
737
- choose one — is the input you render (\`text\`, \`textarea\`, \`email\`, \`tel\`,
738
- \`number\`, \`currency\`, \`checkbox\`, \`select\`). "Build only this" means exactly
739
- that. Ask nothing; build it in one turn.
764
+ \`pageTitle\`) and the starting layout (\`template\`) were set on the Pages
765
+ screen — read them from the \`config\` your content file receives, and never
766
+ write them again. Your component also receives \`actions\` (\`records\`,
767
+ \`saveRecord\`, \`updateRecord\`, \`deleteRecord\`, \`uploadFile\`, \`user\`, and the
768
+ rest listed under presets below) — read and change data ONLY through those,
769
+ never by importing firebase yourself. THE FILE NAME RULE: write EXACTLY the
770
+ file path the request names, letter for letter — the page is wired to that
771
+ one name, and a file under any other name or folder is never found. "No file
772
+ uploads" means draw no upload control; the "button at the top" is yours to
773
+ draw inside the content; and a question's kind in brackets — short text, long
774
+ text, email, phone, number, money, yes-no, choose one — is the input you
775
+ render (\`text\`, \`textarea\`, \`email\`, \`tel\`, \`number\`, \`currency\`,
776
+ \`checkbox\`, \`select\`). "Build only this" means exactly that. Ask nothing;
777
+ build it in one turn.
740
778
 
741
779
  ### A form
742
780
 
@@ -887,19 +925,20 @@ add / upload / delete — you then write the microcomponent that fills it
887
925
 
888
926
  A preset (\`board\`, \`calendar\`, \`table\`, \`drive\`) is only a starting point:
889
927
  it renders a working board / calendar / table / file library straight away when
890
- there is NO content file. As soon as you add a content file, it REPLACES what the preset
891
- draws inside the panel — you keep the preset's title, header and data hooks
892
- (\`board\`: \`tasks\`, \`categories\`, \`saveTask(id, data)\`, \`deleteTask\`,
893
- \`saveCategory\`, \`deleteCategory\`, \`user\`; \`calendar\`: \`events\`,
894
- \`saveEvent\`, \`deleteEvent\`, \`openAddModal\`, \`openEditModal\`, \`currentUser\`;
895
- \`table\`: \`tables\`, \`activeTable\`, \`saveTable\`, \`deleteTable\`, \`updateCell\`,
896
- \`addRow\`, \`addColumn\`, \`user\`; \`drive\`: \`files\`, \`loading\`, \`uploadFile\`,
897
- \`deleteFile\`, \`user\`), and you draw the columns, cards or grid yourself. So when the owner wants their own fields,
898
- stages or views on "something like the board", pick \`template: 'board'\` for
899
- its data and build the board UI in the content file. Never treat a preset as
900
- the finished page — they described what they want, so build that.
901
-
902
- Whatever you save through those hooks, include \`uid: user.uid\` in the data:
928
+ there is NO content file. As soon as you add a content file, the page renders
929
+ YOUR file instead of the preset — the whole panel, its buttons included, is
930
+ yours to draw, and your component receives the same \`actions\` as a
931
+ from-scratch page (\`records\`, \`loading\`, \`saveRecord\`, \`updateRecord\`,
932
+ \`deleteRecord\`, \`startEdit\`, \`confirmDelete\`, \`uploadFile\`, \`openAddForm\`,
933
+ \`setAddForm\`, \`setFileHandler\`, \`user\` — every record you save must include
934
+ \`uid: actions.user.uid\`). The preset is a REFERENCE for what to build, never
935
+ a frame you build inside: when the owner wants their own fields, stages or
936
+ views on "something like the board", look at the board for the shape and
937
+ build the whole board UI in the content file with those \`actions\`. Never
938
+ treat a preset as the finished page — they described what they want, so
939
+ build that.
940
+
941
+ Whatever you save through those actions, include \`uid: user.uid\` in the data:
903
942
  on Your-team pages only the person who created a record (or an admin) may edit
904
943
  or delete it, and a record without \`uid\` can only ever be changed by an admin.
905
944
 
@@ -931,12 +970,12 @@ useEffect(() => {
931
970
  if (userRole !== 'admin') return; // only admins may read the Inbox
932
971
  return onSnapshot(collection(db, 'user_submissions'), snap => {
933
972
  snap.docs.forEach(d => {
934
- if (tasks.some((t: any) => t.sourceId === d.id)) return; // already imported
973
+ if (actions.records.some((r: any) => r.sourceId === d.id)) return; // already imported
935
974
  const s = d.data();
936
- saveTask('', { ...s, sourceId: d.id, stage: 'Prospect', uid: user!.uid, createdAt: Date.now() });
975
+ actions.saveRecord({ ...s, sourceId: d.id, stage: 'Prospect', uid: user!.uid });
937
976
  });
938
977
  });
939
- }, [userRole, tasks.length]);
978
+ }, [userRole, actions.records.length]);
940
979
  \`\`\`
941
980
 
942
981
  Never write to \`user_submissions\` or \`user_requests\` from a page, and never
@@ -65,8 +65,10 @@ export const themeConfig = {
65
65
  foreground: '#1e1b4b', // Primary text color
66
66
 
67
67
  accent: '#7c3aed', // Main branding color (Purple)
68
- panelBg: 'rgba(255, 255, 255, 0.70)', // Card/Panel background
69
- panelBorder: 'rgba(124, 58, 237, 0.08)', // Card/Panel borders
68
+ // A faint accent wash rather than white-on-white: inner cards were
69
+ // invisible against the page in light mode. Kept quiet on purpose.
70
+ panelBg: 'rgba(124, 58, 237, 0.04)', // Card/Panel background
71
+ panelBorder: 'rgba(124, 58, 237, 0.15)', // Card/Panel borders
70
72
 
71
73
  buttonGradient: ['#5b21b6', '#a78bfa'], // [Start, End]
72
74
  secondaryButton: {
package/src/index.css CHANGED
@@ -75,8 +75,10 @@ input[type=number] {
75
75
  --accent-deep: #6d28d9;
76
76
  /* violet-700 */
77
77
 
78
- --panel-bg: rgba(255, 255, 255, 0.7);
79
- --panel-border: color-mix(in srgb, var(--accent-color) 8%, transparent);
78
+ /* A faint accent wash, not white-on-white: inner cards were invisible
79
+ against the page in light mode. Kept quiet on purpose. */
80
+ --panel-bg: color-mix(in srgb, var(--accent-color) 4%, transparent);
81
+ --panel-border: color-mix(in srgb, var(--accent-color) 15%, transparent);
80
82
 
81
83
  --button-bg: linear-gradient(135deg, #5b21b6 0%, #a78bfa 100%);
82
84
  --button-fg: #ffffff;
@@ -192,6 +194,13 @@ h3,
192
194
  0 2px 6px rgba(24, 16, 64, 0.07);
193
195
  }
194
196
 
197
+ /* An open drop-down. Deliberately NOT .glow-focus: the 25px halo read as a
198
+ spotlight on what is just a list being open, and it was sliced off by any
199
+ panel animating its height nearby. An inset ring cannot be clipped. */
200
+ .select-open {
201
+ box-shadow: inset 0 0 0 2px color-mix(in srgb, var(--accent-color) 22%, transparent);
202
+ }
203
+
195
204
  .glow-focus:focus-within {
196
205
  box-shadow: 0 0 25px var(--glow-color, var(--primary-glow));
197
206
  border-color: var(--accent-color);
@@ -224,27 +233,28 @@ h3,
224
233
  }
225
234
 
226
235
  /*
227
- * Fields in the admin consoles keep the accent focus, but tight. A console
228
- * is panels inside panels, and the ones that open and close animate their
229
- * height behind overflow: hidden — so the 15-25px halo above spills past the
230
- * field and gets sliced straight off at the panel edge. A 3px ring sits well
231
- * inside the panel's own padding: softer to look at, and nothing can clip
232
- * it. `.console-field` goes on the box itself; `.console-select` goes on the
236
+ * Fields in the admin consoles keep the accent focus, but tight — and INSET.
237
+ * A console is panels inside panels, and the ones that open and close
238
+ * animate their height behind overflow: hidden, so anything drawn OUTSIDE
239
+ * the field's own box — a halo, even a 3px outer ring — gets sliced off at
240
+ * whichever panel edge is nearest. An inset ring is drawn on the inside of
241
+ * the field's border, so no container can ever clip it, anywhere.
242
+ * `.console-field` goes on the box itself; `.console-select` goes on the
233
243
  * CustomSelect wrapper, whose bordered box is the trigger inside it.
234
244
  */
235
245
  .console-field:focus,
236
246
  .console-field:focus-within,
237
- .console-select .glow-focus:focus-within {
238
- box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent-color) 15%, transparent) !important;
247
+ .console-select .select-open {
248
+ box-shadow: inset 0 0 0 2px color-mix(in srgb, var(--accent-color) 22%, transparent) !important;
239
249
  border-color: var(--accent-color) !important;
240
250
  outline: none;
241
- transform: none;
251
+ transform: none !important;
242
252
  }
243
253
 
244
254
  /* A field that is refusing what was typed stays red while it is focused. */
245
255
  .console-field.is-invalid:focus,
246
256
  .console-field.is-invalid:focus-within {
247
- box-shadow: 0 0 0 3px color-mix(in srgb, #ef4444 18%, transparent) !important;
257
+ box-shadow: inset 0 0 0 2px color-mix(in srgb, #ef4444 25%, transparent) !important;
248
258
  border-color: #ef4444 !important;
249
259
  }
250
260