Custom views
1. Architecture & Where Custom Views Live
Custom Views (custom_views) are interactive HTML + Tailwind CSS + JavaScript micro-applications, dashboards, forms, booking engines, client portals, and landing pages that run inside a hardened <iframe sandbox> (SandboxedViewFrame) connected to Satia's database via an asynchronous postMessage IPC bridge (window.satia).
1.1 Pre-Injected Sandbox Environment (SandboxedViewFrame)
Your code_content is injected directly inside <div id="satia-app-root">...</div> of an already-initialized HTML document. The host <head> automatically pre-loads:
- Tailwind CSS CDN (
https://cdn.tailwindcss.com) configured with Shadcn HSL semantic variables (background,foreground,card,primary,secondary,muted,accent,destructive,border,input,ring). - Google Fonts:
Plus Jakarta Sans(font-sans) andJetBrains Mono(font-mono). - Lucide Icons (
https://unpkg.com/lucide@latest): Use<i data-lucide="icon-name" class="w-4 h-4"></i>. If you render icons dynamically via JavaScript, callif (window.lucide) window.lucide.createIcons();after updating the DOM. - Built-in Utility Classes:
.satia-card,.satia-btn,.satia-btn-primary,.satia-btn-secondary,.satia-btn-outline,.satia-btn-ghost,.satia-btn-destructive,.satia-input,.satia-badge, and.custom-scrollbar. - Optional Libraries: Do NOT re-import Tailwind or Lucide. Only import
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>when building dashboards with canvas charts.
1.2 The 5 Rendering Contexts & Routes (Web + Mobile App)
Every Custom View you create in custom_views works automatically both in the Satia Web Platform and inside the Satia Mobile App (iOS & Android) under Mis Apps (Custom Views), allowing businesses to build their own custom mobile applications (e.g., warehouse barcode scanners, field delivery trackers, POS terminals, or inspection checklists) without publishing a separate mobile app:
- Standalone Micro-App (
entity_slug = None):- Appears in the workspace sidebar under
custom_views.module(e.g.,"Ventas","Operaciones") on Web, and in the Custom Views (Mis Apps) launcher on the Satia Mobile App. - Internal Route:
/views/[slug]-> markdown link:[View Name](/views/my-slug).
- Appears in the workspace sidebar under
- Table-Bound View (
entity_slug = '<table_slug>'):- Appears as an interactive view tab directly inside that business table's header (both on Web and Mobile).
- Internal Route:
/erp/[entity_slug]-> markdown link:[Table Name](/erp/pedidos).
- Public Shareable Link (
shared_view_links):- Accessible by external clients, suppliers, or guests on any desktop or mobile browser without requiring a Satia staff account.
- Public Route:
/shared/view/[token]-> markdown link:[Open Link](/shared/view/abc123token). - Contextual Signed Link (
?ctx=):/shared/view/[token]?ctx=<signed_hmac_token>binds the session to a specific contact (window.satia.context.contact).
- External & Internal Embed Mode (
/shared/view/[token]?embed=1):- Designed for embedding inside external websites (WordPress, Webflow, Shopify) or parent landing pages:
- Sets
html, body { background: transparent !important; }, hides the floating Satia badge and loading overlay, and enablesallow-popups allow-popups-to-escape-sandbox. - Auto-Resize (
ResizeObserver): Automatically observes#satia-app-rootand posts height changes to the parent window. - URL Query Parameters (
window.satia.params): Any URL query parameters passed to the embed URL (excludingctxandembed, up to 20 alphanumeric/_keys of max 500 chars, e.g.,?embed=1&utm_source=meta&plan=pro&landing_path=/promo) are automatically injected intowindow.satia.params. - Parent Window PostMessage Protocol (
SATIA_EMBED): The parent page receives structured messages from the embedded view:{ source: 'SATIA_EMBED', token: '<token>', type: 'ready' }(fired once Tailwind styles settle and initial height is > 0).{ source: 'SATIA_EMBED', token: '<token>', type: 'resize', height: <number> }.{ source: 'SATIA_EMBED', token: '<token>', type: 'event', event: '<eventName>', data: <any> }(triggered whenever the view callswindow.satia.emit(eventName, data)).{ source: 'SATIA_EMBED', token: '<token>', type: 'error', message: '<string>' }.
- Sets
- Designed for embedding inside external websites (WordPress, Webflow, Shopify) or parent landing pages:
- Satia Mobile App Execution (
window.satia.env.platform === 'mobile'):- When opened inside the Satia Mobile App (iOS/Android), the view runs as a native-feeling mobile app with direct access to phone hardware via
window.satia.native(camera QR/barcode scanner, GPS coordinates, biometric FaceID/TouchID, and haptic feedback — see Section 3.8).
- When opened inside the Satia Mobile App (iOS/Android), the view runs as a native-feeling mobile app with direct access to phone hardware via
2. Builder Tools & Surgical Editing Workflow
2.1 Composable Knowledge Tree (discovery__get_ui_design_manual — 6 Specialized Branches)
This manual (03_custom_views_and_design.md) is the Root Trunk: it defines how all Custom Views are saved, secured (data_binding), connected to business tables (getEntities / createEntity / updateEntity / deleteEntity), and styled under the Satia Design System.
Before writing code_content, unlock the specific knowledge branches your view needs by calling discovery__get_ui_design_manual. You can request one branch (category='...') or combine multiple branches in a single call (categories=['...', '...']):
Branch (category) | What It Unlocks | Combine When... |
|---|---|---|
'dashboards' | 4-Layer KPI & Chart architecture (At-a-Glance KPIs → Trends → Top-N Breakdowns → Evidence Table), Chart.js configuration, anti-rainbow rules, and global filter synchronization. | Building executive panels, analytics, financial monitors, or operational boards. |
'forms' | Single-page (≤ 6 fields) vs. genuine multi-step wizard (≥ 7 fields), interactive Choice Cards instead of native <select>, Cal.com-style Date & Time-Slot Pill Grids, Media Dropzones (uploadMedia), inline validation, and Receipt state. | Building booking engines, checkouts, data capture, or request wizards. |
'landing_pages' | Editorial / Swiss / Warm Craft / Bold Poster art directions (Light Mode default), "One Live Thing" interactive artifact grounded in real ERP data, and strict bans on AI clichés. | Building public landing pages, digital menus, catalogs, storefronts, or client portals. |
'auth' | Deep-dive on window.satia.auth: choosing between Passwordless Lead Gate (identify), Full Account Auth (login / register / logout), or Contextual ?ctx= links; onAuthChange reactive lifecycle; hot session migration; and auth UI/UX patterns. | The view requires client login, registration, participant accounts, or a passwordless lead gate. |
'embedded_agents' | Deep-dive on window.satia.agent (channel_type = 'web'): data_binding.agent_id, init, sendMessage, setContext (live screen context injection), onMessage (msg.role === 'model'), onStatus, auth_mode, welcome_message, multimodal media rendering (image, pdf, audio, video), and Standalone Chat vs. Contextual Copilot UX patterns. | Embedding a transactional AI agent as a full chat app, split-screen assistant, or floating copilot. |
'mobile' | Deep-dive on building mobile-first field/operations apps for the Satia Mobile App (window.satia.env.platform === 'mobile') and window.satia.native (scanBarcode, getLocation, authenticateBiometric, haptic), thumb-zone sticky bars, and web fallbacks. | Building POS terminals, barcode/QR scanners, field delivery trackers, check-in kiosks, or mobile-first tools. |
# Example: A public storefront with an embedded AI shopping copilot and a lead gate
discovery__get_ui_design_manual(categories=["landing_pages", "embedded_agents", "auth"])
# Example: A mobile warehouse scanner & inventory form
discovery__get_ui_design_manual(categories=["forms", "mobile"])
2.2 Saving & Updating Custom Views (database__insert & database__update on custom_views)
You create and update views directly on the custom_views table using database__insert and database__update.
- Automatic Upsert by
slug: Callingdatabase__insert(table_name="custom_views", data={...})automatically checks if a view with thatslugalready exists in the workspace; if it does, it updates the existing view transparently. - Virtual
table_slugResolution: Pass"table_slug": "pedidos"(or omit it for standalone sidebar views) to bind the view as an interactive tab inside that business table (/erp/pedidos).
database__insert(
table_name="custom_views",
data={
"name": "Panel Ejecutivo de Ventas",
"slug": "panel-ejecutivo-ventas", # Unique lowercase slug with hyphens (/views/[slug])
"code_content": "<div>...</div>", # Self-contained HTML + Tailwind + <script>
"view_type": "dashboard", # 'dashboard' | 'form' | 'card_grid' | 'custom'
"module": "Ventas", # Sidebar module name (for standalone views)
"table_slug": "pedidos", # Optional: bind as a tab inside a business table slug
"description": "Executive dashboard for revenue, funnel, and top products.",
"icon": "BarChart3", # Lucide icon name
"data_binding": {
"target_entity_slug": "pedidos",
"allowed_actions": ["read", "create", "update"],
"writable_fields": ["estado", "notas", "comprobante"],
"readable_fields": ["*"],
"default_filters": {},
"require_login": False,
"tables": {
"pedidos": {
"permission": "write",
"isolation": "auto",
"column_permissions": {"costo_interno": "hidden"},
"default_filters": {"estado": "neq.borrador"}
},
"productos": {
"permission": "read",
"isolation": "none"
}
}
}
}
)
2.3 Creating Public Share Links (database__insert on shared_view_links)
Create public links by inserting into shared_view_links. You can pass either custom_view_id (UUID) or view_slug (the view's slug, which is auto-resolved to the view's UUID), and token is generated automatically if omitted:
database__insert(
table_name="shared_view_links",
data={
"view_slug": "panel-ejecutivo-ventas", # Or "custom_view_id": "<VIEW_UUID>"
"permission": "write", # 'read' (dashboards/catalogs) or 'write' (forms/portals/POS)
"label": "Public booking link",
"filter_override": {"estado": "activo"} # Optional server-side PostgREST filter override
}
)
Returns token and url_path (/shared/view/<token>).
2.4 Surgical Editing of Existing Views (database__inspect_text + database__patch_text)
When fixing a bug, adjusting styles, or adding features to an existing view in custom_views:
- Inspect in Broad Windows (
150–300lines): Usedatabase__inspect_text(table_name="custom_views", record_id="<view_id>", column_name="code_content", search_pattern="...")to locate anchors, and read broad slices (150–300lines per call rather than tiny 20-line slices) so you have full structural context in a minimal number of tool calls. - Batch Edits in a Single
database__patch_textCall: Group all related edits into a singledatabase__patch_text(..., replacements=[...])call instead of making separate top-to-bottompatch_textcalls. The engine automatically compensates for line-number shifts (line_delta) across multiple replacements in the same call.
3. Complete window.satia JavaScript SDK Reference
The bridge auto-initializes on DOMContentLoaded, retries handshakes if needed, and queues any early requests until INIT completes. Calling await window.satia.init() at the start of your main async function is safe and recommended.
3.1 Record Normalization (normalizeEntityRow) & Automatic Hydration (*_resolved)
Every record returned by getEntities, createEntity, updateEntity, auth.login, and auth.register is automatically normalized and hydrated:
- Dual Access (Root +
attributes): All dynamic column keys are flattened directly onto the root object (row.estado,row.precio,row.cliente_id) AND preserved insiderow.attributes(row.attributes.estado). Native columns (id,name,starts_at,ends_at,created_at,updated_at) take priority at the root when non-null. - Automatic Relation, Tag & Knowledge Hydration (
<key>_resolved):relationcolumns: If a record hascliente_id: "uuid", the engine automatically populatesrow.cliente_id_resolved(androw.attributes.cliente_id_resolved) with{ id, name, type }(or an array of{ id, name, type }for multi-relations). Always renderrow.cliente_id_resolved?.name || 'Unassigned'in UI tables and cards—never display raw UUIDs!tagscolumns: Populatesrow.<tags_key>_resolvedwith an array of{ id, name, color, type: 'tag' }.knowledgecolumns: Populatesrow.<knowledge_key>_resolvedwith{ id, name, title, doc_type, url, file_url, filename, mime_type, size, ai_description, content, metadata }.
- Automatic Cart / Line Items Hydration (
line_items):- On event tables, each
line_itemscolumn (e.g.,productos_seleccionados) is automatically hydrated fromevent_itemsas an array of item objects directly atrow.productos_seleccionados(androw.attributes.productos_seleccionados).
- On event tables, each
3.2 Core Data & Schema Methods
await window.satia.init()Initializes the bridge and returns{ status: 'connected', session: window.satia.context, params: window.satia.params }.await window.satia.getEntities(entitySlug, filters = {})Fetches records forentitySlugordered bycreated_at DESC.- Important Filter Rule: In
getEntities(slug, filters), the client-sidefiltersobject supports{ filter_by_current_contact: true }(which filters server-side by the authenticatedcontact_idinwindow.satia.context.contact.id). For arbitrary UI search/category/date filtering, filter the returned array in JavaScript (rows.filter(...)), or configure permanent server-side filters indata_binding.default_filters/data_binding.tables[slug].default_filters/shared_view_links.filter_override. - Satia Docs Shortcut: Calling
getEntities('knowledge_docs' | 'archivos' | 'recursos' | 'files' | 'assets', filters)automatically routes togetAssets(filters).
- Important Filter Rule: In
await window.satia.getSchema(entitySlug?)Returns{ id, name, slug, type, module, icon, description, attributes: [{ id, key, label, type, is_required, is_unique, config, sort_order }] }(filtered byreadable_fieldsand excluding'hidden'columns).await window.satia.createEntity(entitySlugOrData, maybeData?)Supports bothcreateEntity('pedidos', { ... })andcreateEntity({ ... })(usestarget_entity_slug).- Automatic Contact Binding: If a contextual/authenticated session (
window.satia.context.contact.id) is active, creating an event record automatically setscontact_id,cliente: [contactId], andcontact: [contactId]. - Smart Contact Deduplication: If
createEntitytargets acontacttable while a contact session (contact_id) is active, the backend updates/merges the existing contact record instead of creating a duplicate! - Automatic Schedule Calculation: Passing an ISO string or
{ start, end }to ascheduleattribute automatically populates physicalstarts_atandends_at(calculatingends_atfromdefault_duration_minuteswhen configured). - Automatic Line Items Creation: Passing an array of cart items (
[{ offering_id, name, unit_price, quantity, attributes }]) in aline_itemsattribute key automatically inserts the rows intoevent_itemswith the correspondingattribute_key. - Meta Lead Tracking: Submitting a form on a public link automatically triggers
trackSatiaFormLeadfor conversion tracking.
- Automatic Contact Binding: If a contextual/authenticated session (
await window.satia.updateEntity(entitySlugOrId, idOrData, maybeData?)Supports bothupdateEntity('pedidos', recordId, { estado: 'pagado' })andupdateEntity(recordId, { estado: 'pagado' }).- Performs a safe server-side JSONB merge on
attributes(never overwrites unspecified fields). - If a
line_itemsarray is included in the update payload, it atomically replaces theevent_itemsfor thatattribute_key.
- Performs a safe server-side JSONB merge on
await window.satia.deleteEntity(entitySlugOrId, maybeId?)Supports bothdeleteEntity('pedidos', recordId)anddeleteEntity(recordId).- Public Link Guardrail: On public links (
shared_view_links),deleteEntityis blocked by default unless'delete'is explicitly included indata_binding.allowed_actions.
- Public Link Guardrail: On public links (
3.3 Media & Satia Docs Assets Methods
await window.satia.uploadMedia(fileInputOrFile, entitySlug = 'general', attributeKey = 'media')Accepts a<input type="file">DOM element, aFileList, or aFile/Blobobject directly. Serializes the binary viaArrayBufferoverpostMessage, uploads it to Supabase Storage (satia-media), and returns aMediaItem:{ url: "https://...", name: "recibo.jpg", type: "image" | "video" | "audio" | "document", mime_type: "image/jpeg", size: 12345 }. Save[mediaItem](array) ormediaIteminto your table'smediacolumn viacreateEntity/updateEntity. (Note: On public links, requirespermission: 'write'and, ifwritable_fieldsis non-empty,attributeKeymust be listed inwritable_fields).window.satia.getMediaUrl(mediaAttr, fallback = '')Safely extracts the first valid URL from anymediaorknowledge_resolvedattribute (whether it is aMediaItemarray, a single object, or a direct URL string) and resolves relative paths.await window.satia.getAssets(filter = {})Queries standalone files (doc_type = 'file') from Satia Docs (knowledge_docs). Supportsfilter:{ search: 'logo', folder_id: '...', tag: '...', mime_type: 'image/' }. Returns normalized asset objects with{ id, title, name, url, file_url, filename, mime_type, size, ai_description, tags, tags_resolved, folder_id }.await window.satia.getAssetUrl(nameOrTag, fallback = '')Smart asset resolver with in-memory caching: searches Satia Docs standalone files by exact ID, substring match intitle,filename,tags, orai_description, or word-token match. If querying'satia'or'logo'with no custom brand logo uploaded, automatically falls back to the host's/SATIA.svg(or/SATIA_blanco.svgif'blanco'/'white'/'dark'is in the query). (Bonus: The sandbox also runs aMutationObserverand<img>error listener that automatically resolves relative or broken<img>srcattributes against Satia Docs assets).
3.4 Native End-User Authentication & Lead Gates (window.satia.auth) — Unlock category='auth'
MANDATORY RULE: Whenever a Custom View requires login, registration, participant accounts, or passwordless lead identification, ALWAYS use
window.satia.authand calldiscovery__get_ui_design_manual(category="auth")(or include"auth"incategories=[...]) for the complete contract and UI/UX patterns. NEVER build DIY login lookups usinggetEntities()/createEntity().
- Core Methods Overview:
await window.satia.auth.identify({ name, phone?, email?, entitySlug?, ...customAttrs })— Passwordless Lead Gate (captures/merges contact and issues a 24h signed session).await window.satia.auth.register({ identifier, password, name?, entitySlug?, ...customAttrs })— Creates ascrypt-hashed account (or merges seamlessly if identifier + password match).await window.satia.auth.login({ identifier, password, entitySlug? })— Verifies credentials and persists a 24hHMAC-SHA256session inlocalStorage.await window.satia.auth.logout()— Clears session and firessatia:auth-changed.window.satia.auth.getUser(),window.satia.auth.getSession(),window.satia.auth.isAuthenticated(),window.satia.auth.onAuthChange(cb)— Session inspection and reactive UI state listeners.
3.5 Business Timezone Date Helpers (window.satia.dates)
All dates are formatted in the workspace's configured IANA timezone (window.satia.dates.timezone, e.g., "America/Bogota"):
window.satia.dates.getToday()->"YYYY-MM-DD"in business local time.window.satia.dates.toUTC(dateStr, timeStr = '12:00')-> Converts local"YYYY-MM-DD"+"HH:MM"to an ISO 8601 UTC string ("2026-04-15T14:00:00.000Z") ready to save inschedule/starts_at.window.satia.dates.format(date, options?)-> Localized human date string (es-CO).window.satia.dates.formatTime(date, hour12 = true)-> Localized time string (e.g.,"9:30 a. m.").window.satia.dates.extract(dateOrRecord)-> Inspects an ISO string or a record (starts_at,fecha_de_reserva,fecha,created_at) and returns{ ymd, timeStr, hour, minute, hourDecimal, dateObj }in business local time.
3.6 Shopping Cart & Currency Helpers (window.satia.cart)
window.satia.cart.calculateTotal(items)-> SumsNumber(item.unit_price ?? 0) * Number(item.quantity ?? 1).window.satia.cart.calculateCount(items)-> SumsNumber(item.quantity ?? 1).window.satia.cart.formatItem(item, options?)-> Normalizes{ offering_id, name, unit_price, quantity, attributes }.window.satia.cart.formatCurrency(amount, currency?, locale?)-> Formats numbers/currencies usingIntl.NumberFormat.
3.7 UI Feedback, Modals & Embed Events
window.satia.toast(message, type = 'info'): Displays a native toast notification in the host app ('success' | 'error' | 'info' | 'warning'). Never use browseralert()!window.satia.getParam(key, fallback = ''): Reads a URL query parameter fromwindow.satia.params(e.g.,const utm = window.satia.getParam('utm_source', 'direct')).window.satia.emit(eventName, data = {}): Emits a custom event to the parent host or embedding page (SATIA_EMBED).- DOM Helpers:
window.satia.openModal(id),window.satia.closeModal(id),window.satia.selectTab(tabGroupId, activeTabId),window.satia.toggleAccordion(id).
3.8 Satia Mobile App Capabilities (window.satia.native) — Unlock category='mobile'
When opened inside the Satia Mobile App (iOS & Android), window.satia.env.platform === 'mobile' is set and window.satia.native exposes phone hardware (scanBarcode(), getLocation(), authenticateBiometric(reason), haptic(style)).
- Mandatory Discovery: Whenever building POS terminals, barcode/QR scanners, field delivery trackers, on-site check-in kiosks, or mobile-focused views, call
discovery__get_ui_design_manual(category="mobile")(or include"mobile"incategories=[...]) for the complete hardware bridge patterns, thumb-zone ergonomics, and mandatory web fallbacks.
3.9 Embedded Transactional Agents & Web Chat (window.satia.agent) — Unlock category='embedded_agents'
You can embed any transactional agent (type = 'transactional') directly inside a Custom View over the dedicated channel_type = 'web' channel—without writing any automation_scripts (on_inbound / on_outbound):
- Mandatory Discovery: Whenever a view embeds a conversational agent or AI copilot, call
discovery__get_ui_design_manual(category="embedded_agents")(or include"embedded_agents"incategories=[...]) for the full contract (data_binding.agent_id,init,sendMessage,setContext,onMessagewith strictmsg.role === 'model',onStatus,cancel,reset,auth_mode,welcome_message, and multimodal media rendering).
4. Multi-Layer Security, data_binding & PII Shield
Public views (/shared/view/[token]) enforce strict server-side security in actions.ts:
4.1 Table Scope Validation (isEntityAllowedInPublicView)
A public view can ONLY query or mutate a business table if that table's slug is authorized by at least one of:
- The view's bound
entity_slugordata_binding.target_entity_slug. - An entry in
data_binding.tables[slug]. - A literal call in
code_content(e.g.,window.satia.getEntities('productos')). Arbitrary requests to unreferenced tables are rejected by the server.
4.2 Per-Table Policies (data_binding.tables) & Isolation ('auto' vs 'none')
Each table in data_binding.tables supports:
{
"tables": {
"citas": {
"permission": "write",
"isolation": "auto",
"column_permissions": {
"notas_internas": "hidden",
"precio": "read",
"fecha": "write"
},
"default_filters": {
"estado": "neq.cancelada"
}
}
}
}
isolation: 'auto'(Default):- On
update,update_status, anddelete, requires an authenticated contact session (contact_id) and verifies that the record belongs to thatcontact_id(row.id === contact_idfor acontacttable, orattributes.contact_id/cliente/contactmatchescontact_idfor aneventtable).
- On
isolation: 'none': Allows public read/write according topermissionandcolumn_permissionswithout requiring row ownership (e.g., public catalogs, open check-in boards).require_login: true(at root ofdata_binding): BlocksgetEntities(returns[]) and all mutations until the user logs in viawindow.satia.author opens a signed?ctx=link.column_permissions: Map ofcolumn_key -> 'hidden' | 'read' | 'write'.'hidden'strips the field (and<key>_resolved) fromgetEntitiesandgetSchema;'read'strips the field fromcreateEntityandupdateEntitypayloads.- Dangerous Keys Blacklist: The server always strips
password,password_hash, andplatform_identifiersfrom all public reads and writes.
4.3 Automatic Contact PII Shield on Public Links
When a public link queries a contact table (e.g., a tournament leaderboard or participant directory), the server automatically strips sensitive PII (phone, telefono, whatsapp, celular, movil, email, correo) from all rows that do NOT belong to the currently logged-in contact (row.id !== resolvedContactId), UNLESS you explicitly whitelist that field in column_permissions ({"telefono": "read"}) or readable_fields (["name", "telefono"]).
4.4 Server-Side Filter Syntax (default_filters & filter_override)
Both data_binding.default_filters, data_binding.tables[slug].default_filters, and shared_view_links.filter_override execute server-side in PostgreSQL before data ever reaches the browser:
- Supports PostgREST operators:
eq.<val>,neq.<val>,gt.<val>,gte.<val>,lt.<val>,lte.<val>,like.<val>,ilike.<val>,in.<val1,val2>. (Plain values default toeq). - Supports the dynamic session placeholder
{{contact_id}}, which is replaced at runtime with the verifiedcontact_idfrom?ctx=orwindow.satia.auth(e.g.,{"cliente_id": "eq.{{contact_id}}"}).
5. Contextual Signed Links (?ctx=) for Transactional Agents
Transactional agents (on WhatsApp, Instagram, Web, etc.) can generate personalized, cryptographically signed links (HMAC-SHA256, 24-hour expiration) for the contact they are currently chatting with using the store tool store_custom_views__generate_link:
- How to enable it for an agent: Install the Custom Views store tool (
tool_key = 'custom_views') on the transactional agent viastore__install_tool. - Tool Parameters (
store_custom_views__generate_link):view_slug(required): Exactslugof the target Custom View.prefill_data(optional JSON string or dict): Key-value pairs to pre-populate in the view (exposed atwindow.satia.context.prefill).permission(optional):'write'(default) or'read'.
- What happens when the client opens
/shared/view/[token]?ctx=<hmac>:window.satia.context.contactis pre-populated with{ id, name, phone, attributes }.window.satia.context.prefillcontains theprefill_datapassed by the agent.- Any record created via
window.satia.createEntityis automatically linked tocontact.id(and if the form updates the contact table, it merges into the existing contact without creating duplicates).
6. Official Satia Design System ("Black and white is the brand. Color is the answer.")
Every Custom View built for Satia must follow the official brand Design System unless the user explicitly asks to use their own company logo colors.
6.1 The 90 / 10 Proportion (60% White · 30% Black · 10% Color)
- 60% White Card (
#FFFFFF): Primary page background and base containers. - 30% Black Card (
#000000): High-impact hero cards, featured headers, primary buttons, and focus panels. - Structural Neutrals (Only 3 Grays Allowed):
satia-gris-linea:#E8E8E8— The only color for1px solid #E8E8E8dividing borders.satia-gris-fondo:#F5F5F5— Secondary module backgrounds, input fills, or light hover states.satia-texto-apagado:#A3A3A3— Secondary metadata, subtitles, or placeholders.
- Strict Prohibitions:
- ❌ Zero
box-shadow(drop-shadows) on cards or buttons. Spatial separation is achieved via1px solid #E8E8E8borders or pure#FFFFFFvs.#000000contrast. - ❌ Zero "soft blacks" or bluish grays (
#050811,slate-900,gray-800). Satia black is pure#000000. - ❌ Zero mid-gray body text (
#555,#666). Body text is#000000on white or#FFFFFFon black.
- ❌ Zero
6.2 The 9 Official Secondary Brand Colors (In Strict Brand Sequence)
The 10% of color lives in status badges, charts, indicators, avatars, and interactive accents. When displaying multiple series, badges, or categories, always follow this exact sequence:
| Order | Official Name | Token | Hex | Primary Semantic Usage |
|---|---|---|---|---|
| 1 | Jade | --satia-jade | #00CFB5 | Success, active, completed, primary series #1 |
| 2 | Cenote | --satia-cenote | #08C6D2 | Information, sync, speed, series #2 |
| 3 | Talavera | --satia-talavera | #4381CF | Links, financial analytics, series #3 |
| 4 | Jacaranda | --satia-jacaranda | #8B6FC9 | AI, agents, automation, multipliers, series #4 |
| 5 | Bugambilia | --satia-bugambilia | #ED71BE | Creative highlights, marketing, series #5 |
| 6 | Grana | --satia-grana | #E04264 | Critical error, cancelled, urgent, red alert, series #6 |
| 7 | Cempasúchil | --satia-cempasuchil | #FF993E | Warning, pending review, series #7 |
| 8 | Maíz | --satia-maiz | #FFD949 | In progress, waiting, star/reward, series #8 |
| 9 | Limón | --satia-limon | #D3E736 | New feature, growth, high performance, series #9 |
Text Contrast Rule on the 9 Brand Colors: On top of any of the 9 secondary colors, text and icons are always pure Black
#000000(never white).
6.3 Official Typography (Onest + Plus Jakarta Sans)
Always include the two official fonts from Google Fonts in the <head> of your Custom Views:
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Onest:wght@500;700;800&family=Plus+Jakarta+Sans:wght@400;500;600;700&display=swap" rel="stylesheet">
Onest(font-display): Used for all headings (h1–h3, weight700,letter-spacing: -0.03em) and large numeric KPIs (font-weight: 800,font-variant-numeric: tabular-nums).Plus Jakarta Sans(font-body): Used for body copy (15px), tables, forms, labels (13px, weight600), and buttons.- Sentence case always: Write "Orders of the day" or "Pedidos del día" instead of "ORDERS OF THE DAY". Avoid all-caps with wide letter-spacing.
- Pill Buttons (
border-radius: 9999px/rounded-full):- Primary on white surface: background
#000000, text#FFFFFF,padding: 10px 20px,font-weight: 600. - Primary on black card: background
#FFFFFF, text#000000(or#00CFB5Jade background with#000000text). - Secondary: background
#F5F5F5, text#000000, border1px solid #E8E8E8.
- Primary on white surface: background
7. Official Sidebar Icons (SATIA_ICONS)
When inserting or updating custom_views.icon, choose an icon name from the official Lucide catalog supported by the Satia Sidebar:
- Operations & Commerce:
ShoppingBag,ShoppingCart,Package,Truck,Store,Utensils,Coffee,Receipt,CreditCard,DollarSign,Wallet,Tag,Barcode,Boxes. - People & CRM:
Users,UserCheck,UserPlus,Contact,HeartHandshake,Building2,Briefcase,Award,Stethoscope,GraduationCap. - Calendar & Tasks:
Calendar,CalendarClock,Clock,CheckSquare,ListTodo,ClipboardList,Kanban,Timer,Bell. - Analytics & Dashboards:
LayoutDashboard,BarChart3,LineChart,PieChart,TrendingUp,Activity,Target,Gauge,Sparkles. - Documents & Media:
FileText,FolderKanban,BookOpen,Image,Camera,QrCode,MapPin,Globe,Wrench,Settings.
8. Pre-Save Verification Checklist (custom_views)
- Table Inspection First: Did you call
database__describe_tableson every table used by the view to verify exact column keys,statusoptions, andrelationtargets? - Complete Self-Contained HTML: Does
code_contentstart with<!DOCTYPE html>and include Tailwind CDN +OnestandPlus Jakarta Sansfonts? data_bindingAlignment: Doesdata_binding.tablesauthorize every business table slug referenced inwindow.satia.getEntities/createEntity/updateEntity/deleteEntity?- Mobile & Web Ready: Are grids responsive (
grid-cols-1 md:grid-cols-3) and arewindow.satia.nativefeatures guarded with graceful web fallbacks? - Surgical Edits Only on Existing Views: If modifying an existing view, did you use
database__inspect_text+database__patch_textinstead of rewriting the entirecode_content?