@edgedev/create-edge-app 1.2.34 → 1.2.35

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/README.md CHANGED
@@ -38,6 +38,7 @@ Use the provided scripts instead of manual edits:
38
38
  ./edge-pull.sh
39
39
  ./edge-push.sh
40
40
  ```
41
+ If the `edge-vue-components` remote is missing locally, these scripts auto-add it.
41
42
 
42
43
  ## Deep links (iOS)
43
44
  Add URL types to your `Info.plist` when configuring deep links:
package/agents.md CHANGED
@@ -68,15 +68,108 @@ Adjust props (search, filters, pagination, save overrides) using the existing co
68
68
  - shadcn components live under `components/ui` and Edge-wrapped variants under `edge/components/shad`. Prefer the Edge variants to keep styling consistent and take advantage of shared props/slots.
69
69
  - Styling: stick to Tailwind utility classes alongside the Edge components; when building new components, use Tailwind + `cn` for class composition instead of custom CSS where possible.
70
70
 
71
+ ## CMS Block System Contract
72
+ - Treat the Block Editor help in `edge/components/cms/blockEditor.vue` as the source-of-truth for CMS block behavior.
73
+ - A block is HTML plus special tags; tags with a `field` create editable inputs.
74
+ - Only triple-brace tags create CMS inputs (for example `{{{#text ...}}}`); plain `{{...}}` placeholders do not.
75
+ - Tag config must be valid JSON (double quotes, commas, and exact opening/closing tag format).
76
+ - `field` is the stored key, `value` is the default, and `title` is the editor label.
77
+ - Field order in the editor follows first appearance order in the template.
78
+ - On edit, template meta and stored meta are merged; persisted filters/limits should be preserved.
79
+ - If a field is removed from template markup, existing stored data remains and returns when the field is added back.
80
+
81
+ ### Block fields and rendering
82
+ - Core field types used by CMS blocks: `text`, `textarea`, `richtext`, `number`, `image`, `array`.
83
+ - `text` and `textarea` are HTML-escaped on render.
84
+ - `richtext` renders as HTML.
85
+ - Inline formatter output is HTML-escaped by default.
86
+ - Image field values store URL strings; optional `tags` limits media picker scope.
87
+ - Validation supports `required`, `min`, `max` (numeric bounds for numbers, length/count for text/arrays).
88
+ - Preview behavior: empty fields use placeholders; empty arrays may show sample items.
89
+ - The JSON Field Editor is line-driven in the code editor and is the preferred fix path for broken tag JSON.
90
+
91
+ ### Inline formatter support
92
+ - Supported formatters: `date`, `datetime`, `money`, `lower`, `upper`, `trim`, `slug`, `title`, `deslug`, `default`.
93
+ - Use inline formatters directly in placeholders (for example `{{ date(post.publishDate) }}`).
94
+ - Existing schema/meta formatting behavior remains valid alongside inline formatters.
95
+
96
+ ### Array blocks
97
+ - Manual arrays can use scalar values or object schemas; supported schema field types include `text`, `textarea`, `richtext`, `image`, `number`, `option`.
98
+ - Manual array UI includes add, reorder, and delete controls; `limit` caps rendered item count.
99
+ - Array loop aliases use `as` (for example `{{{#array {"field":"items","as":"card"}}}}`).
100
+ - Use `subarray` for nested arrays and `entries` for object key/value iteration.
101
+ - Use `renderBlocks` when an item contains nested CMS block content (for example post body content).
102
+
103
+ ### Array data loading (Firestore/API)
104
+ - Firestore arrays use `collection` config with `path`, `uniqueKey`, `query`, `order`, `limit`.
105
+ - `path` is org-scoped under `organizations/{orgId}`.
106
+ - `uniqueKey` supports template tokens like `{orgId}` and `{siteId}`.
107
+ - API arrays use `api`, optional `apiQuery`, and `apiField`.
108
+ - `queryOptions` creates CMS filter controls; selected values are stored in `meta.queryItems`.
109
+ - Loading state tokens are supported while async array data resolves: `{{loading}}` and `{{loaded}}`.
110
+
111
+ ### Array query execution model (critical)
112
+ - Each `queryItems` entry performs its own KV index lookup via `kvClient.queryIndex`.
113
+ - Query keys only work if the field is present in KV mirror config: include keys in `indexKeys`, and also in `metadataKeys` when needed for rendering/sorting.
114
+ - Multiple `queryItems` are unioned first (OR behavior at lookup stage).
115
+ - Candidate duplicates are removed by canonical key.
116
+ - `collection.query` then runs in JavaScript as final filtering (AND behavior across rules).
117
+ - `collection.order` sorts the filtered result set.
118
+ - Final output is written to `values[field]`.
119
+ - If load fails, runtime falls back to inline `value` or `[]` when no fallback value exists.
120
+
121
+ ### Firestore index + KV mirror requirements
122
+ - Any Firestore compound query used by blocks (for example array contains + order) requires matching composite indexes in `firestore.indexes.json`.
123
+ - Fast CMS filtering requires KV mirroring in Functions with both index and metadata coverage.
124
+ - Mirror pattern example:
125
+ ```js
126
+ exports.onListingWritten = createKvMirrorHandlerFromFields({
127
+ documentPath: 'organizations/{orgId}/listings',
128
+ uniqueKey: '{orgId}',
129
+ indexKeys: ['name', 'city', 'state', 'status'],
130
+ metadataKeys: ['name', 'city', 'state', 'status', 'price', 'doc_created_at'],
131
+ })
132
+ ```
133
+
134
+ ### Conditional/template logic
135
+ - `if/else` conditionals are supported in templates with `cond`.
136
+ - Array/subarray conditionals can reference `item.*` and alias objects.
137
+ - Supported conditional operators: `==`, `!=`, `>`, `<`, `>=`, `<=`.
138
+
139
+ ### CMS interactive helper systems
140
+ - Carousel blocks: runtime auto-inits Embla from `[data-carousel]` markup, with `[data-carousel-track]` and slide basis classes required.
141
+ - Carousel options are class/data-attribute driven (`data-carousel-autoplay`, interval, loop, fade, responsive `slides-to-scroll`).
142
+ - Carousel behavior details: when loop is off use trim snaps behavior, dot count is snap-based, controls/dots are rebuilt on `reInit`, and initialized roots are tagged (`data-embla="true"`).
143
+ - Nav blocks: runtime helper classes (`cms-nav-root`, `cms-nav-toggle`, `cms-nav-panel`) drive interactive menu behavior in preview/runtime.
144
+ - Nav helper classes also support folder dropdowns, sticky behavior, hide-on-scroll behavior, and left/right/center positioning.
145
+ - Nav class contract to remember: `cms-nav-root` + `cms-nav-toggle` + `cms-nav-panel` are required; `cms-nav-overlay`, `cms-nav-close`, `cms-nav-link`, `cms-nav-folder*`, `cms-nav-main`, `cms-nav-layout`, `cms-nav-logo`, `cms-nav-desktop` are optional hooks.
146
+ - Nav root attributes supported by runtime include: open state/class, close-on-link, position, scrolled/top class pairs, row class pairs, scroll threshold (default 10), hide-on-down toggles/thresholds/delta (defaults 80/6), and hidden/visible/transition class overrides.
147
+ - Form helpers: `form.cms-form` or `[data-cms-form]` submits to `/api/contact` with anti-bot checks and history tracking.
148
+ - Form helper hooks include required markers, submit trigger, and message containers; context IDs are inherited automatically from block wrapper.
149
+ - Form helper defaults: endpoint defaults to `/api/contact`; success/error/required copy and state classes are configurable through `data-cms-*` attributes; Block Editor preview is structure/UX only, not delivery verification.
150
+ - Scroll reveal helpers use class contracts (`sr`, `sr-group`, `sr-item`) plus utility classes for direction, duration, distance, stagger, viewport trigger, reset/cleanup, device targeting, easing, and optional callbacks.
151
+ - Scroll reveal defaults to remember: `origin: bottom`, `distance: 24px`, `duration: 700`, `viewFactor: 0.15`, `reset: false`, `cleanup: false`, `mobile: true`, `desktop: true`; callback hook classes map to `window.__srCallbacks`.
152
+
71
153
  ## Do/Don't
72
154
  - Do reuse Edge components (`dashboard`, `editor`, `cms` blocks, auth widgets) before adding new ones.
73
155
  - Do keep Firestore paths, queries, and role checks consistent with `edgeGlobal` helpers (`isAdminGlobal`, `getRoleName`, etc.).
74
156
  - Do use the `edge-*.sh` scripts (like `edge-pull.sh` and `edge-components-update.sh`) to sync/update the `edge` subtree instead of manual edits.
75
157
  - Don’t introduce TypeScript, Options API, raw Firebase SDK calls, or ad-hoc forms/tables when an Edge component exists.
76
- - Don’t edit code inside the `edge` folder unless absolutely required; it is a shared repo. If a change is unavoidable, keep it generic (no project-specific hacks) and call out the suggestion instead of making the edit when possible.
158
+ - Don’t edit code inside the `edge` folder (including `edge/components/cms/*`) unless absolutely required and you have asked for and received user permission for that specific edit every time; it is a shared repo.
159
+ - If an `edge/*` change is unavoidable, keep it generic (no project-specific hacks) and call out the suggestion instead of making the edit when possible.
77
160
  - Don’t modify `storage.rules` or `firestore.rules`.
78
161
 
79
162
  ## Firebase Functions guidance
80
- - Review `functions/config.js`, `functions/edgeFirebase.js`, and `functions/cms.js` to mirror established patterns, but do not edit those files.
163
+ - Review `functions/config.js`, `functions/edgeFirebase.js`, and `functions/cms.js` to mirror established patterns.
164
+ - Only edit `functions/config.js`, `functions/edgeFirebase.js`, or `functions/cms.js` when absolutely required and only after asking for and receiving user permission each time.
81
165
  - When adding new cloud functions, create a new JS file under `functions/` and export handlers using the shared imports from `config.js`. Wire it up by requiring it in `functions/index.js` (same pattern as `stripe.js`), instead of modifying restricted files.
82
166
  - For every `onCall` function, always enforce both checks up front: `request.auth?.uid` must exist, and `request.data?.uid` must exactly match `request.auth.uid`. Throw `HttpsError('unauthenticated', ...)` when auth is missing and `HttpsError('permission-denied', ...)` when the uid does not match.
167
+
168
+ ## Public Publications Contract
169
+ - Public publication rendering is callable-only in this repo: `pages/publications/[org]/[pub].vue` should use `publicAccess-getOrgBySlug`, `publicAccess-getMagazineBySlug`, and `publicAccess-getArticles`, and should not rely on `/api/publications`.
170
+ - Organization docs should include Glossee-compatible profile fields maintained by `functions/organizations.js`: `docId`, `slug`, `about`, `location`, `logo_url`, `website`, `social_links`.
171
+ - `functions/publicAccess.js` is the source of truth for unauthenticated publication visibility (`published` + flipbook detection). Keep this logic centralized; do not duplicate access rules in page code.
172
+ - `functions/publications.js` controls KV mirroring/indexing for files. Preserve status/slug/name/flipbook indexes when refactoring.
173
+ - When changing any of these files, run:
174
+ - `npm run test:publications-public-access`
175
+ - `npm run test:model-import`
package/deploy.sh CHANGED
@@ -99,6 +99,57 @@ resolve_functions_region() {
99
99
  echo "us-west1"
100
100
  }
101
101
 
102
+ resolve_project_id() {
103
+ if [ -n "${PROJECT_ID:-}" ]; then
104
+ echo "$PROJECT_ID"
105
+ return
106
+ fi
107
+
108
+ if [ -n "${FIREBASE_PROJECT_ID:-}" ]; then
109
+ echo "$FIREBASE_PROJECT_ID"
110
+ return
111
+ fi
112
+
113
+ if [ -f ".firebaserc" ]; then
114
+ local rc_project
115
+ rc_project="$(
116
+ node - <<'NODE'
117
+ const fs = require('fs')
118
+ const path = '.firebaserc'
119
+ try {
120
+ const json = JSON.parse(fs.readFileSync(path, 'utf8'))
121
+ const projectId = json?.projects?.default || ''
122
+ process.stdout.write(projectId)
123
+ }
124
+ catch {
125
+ process.stdout.write('')
126
+ }
127
+ NODE
128
+ )"
129
+ if [ -n "$rc_project" ]; then
130
+ echo "$rc_project"
131
+ return
132
+ fi
133
+ fi
134
+
135
+ if [ -n "${GCLOUD_PROJECT:-}" ]; then
136
+ echo "$GCLOUD_PROJECT"
137
+ return
138
+ fi
139
+
140
+ if [ -n "${GOOGLE_CLOUD_PROJECT:-}" ]; then
141
+ echo "$GOOGLE_CLOUD_PROJECT"
142
+ return
143
+ fi
144
+
145
+ if [ -n "${CLOUDSDK_CORE_PROJECT:-}" ]; then
146
+ echo "$CLOUDSDK_CORE_PROJECT"
147
+ return
148
+ fi
149
+
150
+ echo ""
151
+ }
152
+
102
153
  ensure_callable_public_access() {
103
154
  if ! command -v gcloud >/dev/null 2>&1; then
104
155
  echo "Deploy aborted: gcloud CLI not found. It is required to enforce callable invoker access." >&2
@@ -107,6 +158,13 @@ ensure_callable_public_access() {
107
158
 
108
159
  local region
109
160
  region="$(resolve_functions_region)"
161
+ local project_id
162
+ project_id="$(resolve_project_id)"
163
+
164
+ if [ -z "$project_id" ]; then
165
+ echo "Deploy aborted: unable to resolve project id for callable invoker enforcement." >&2
166
+ exit 1
167
+ fi
110
168
 
111
169
  local callable_functions
112
170
  callable_functions="$(
@@ -146,12 +204,13 @@ NODE
146
204
  return
147
205
  fi
148
206
 
149
- echo "Ensuring public invoker access for callable functions in region '${region}'..."
207
+ echo "Ensuring public invoker access for callable functions in region '${region}' (project '${project_id}')..."
150
208
  while IFS= read -r function_name; do
151
209
  [ -z "$function_name" ] && continue
152
210
  echo " - ${function_name}"
153
211
  gcloud functions add-invoker-policy-binding "$function_name" \
154
212
  --gen2 \
213
+ --project "$project_id" \
155
214
  --region "$region" \
156
215
  --member="allUsers" >/dev/null
157
216
  done <<< "$callable_functions"