MolinoPro

tally-forms-integrations-2part_akill

Master Codebase Guidebook
Markdown + HTML Dev-Docs Renderer - Frontend Client Module

Default Index
Open README.md
Root: README.mdintegrations
Milestones

Great questions. You’re now touching the real power layer of what you’re building: turning projects / spaces / documents into programmable business environments, not just pages.

I’ll answer in three stacked layers, from what you already have → what this enables → what’s realistic vs not with Tally.

0️⃣ First: mental model reset (important)

Think in roles, not tools: • Projects / Spaces → Business containers • Documents / Page Builder → Presentation + orchestration layer • Forms (Tally or internal) → Structured input adapters • AI → Optional transformer / assistant • Pipelines → Deterministic workflows

Tally is not a feature. Tally is an input adapter that plugs into Projects & Spaces.

Once you see that, everything becomes clean.

1️⃣ How Tally integrates with Projects & Spaces (cleanly)

Canonical integration point

You already found the right place:

Forms do NOT belong to pages Forms belong to entities (Project / Space / Document)

So the integration is:

Project | Space ↓ FormConfig (DB) ↓ Page Builder / Document Section ↓ Tally.openPopup() OR internal form ↓ Server Action → Pipeline

Minimal DB model you need (very small)

model FormConfig { id Int @id @default(autoincrement()) ownerType String // "project" | "space" | "global" ownerId Int? provider String // "tally" | "internal" externalId String // Tally formId name String purpose String // "trip-intake" | "lead" | "custom" mappingJson Json // field → entity mapping createdAt DateTime @default(now()) }

This single table unlocks everything.

How this appears in the Page / Doc builder

As a section type

You already do this with Trip, LineItems, Planner.

Add one more:

type FormSection = { id: string; kind: "form"; formConfigId: number; mode: "popup" | "embedded"; onSubmitAction: string; // server action key };

Now in Documents2Canvas or PageBuilder:

<FormSectionRenderer section={section} />

FormSectionRenderer (simplified)

"use client";

export function FormSectionRenderer({ section }: any) { const { formConfig } = useFormConfig(section.formConfigId);

if (formConfig.provider === "tally") { return ( <button onClick={() => window.Tally.openPopup(formConfig.externalId, { hiddenFields: { ownerType: formConfig.ownerType, ownerId: formConfig.ownerId, }, }) } > Open Form </button> ); }

return <InternalForm config={formConfig} />; }

📌 This means: • The same document can use different forms • A Project can switch forms without code • A Space can define its own intake logic

2️⃣ What this unlocks (this is the big value)

Once forms are configurable per Project / Space, you unlock:

🧩 Per-project business logic (no code duplication)

Example: • Project A (Travel agency): • Trip intake form • Auto-generate Trip Document • Auto-calc pricing • Auto-create Offer • Project B (Coach / Consultant): • Client onboarding form • Auto-generate Proposal Document • Auto-fill intro letter • No trips at all

Same platform. Different pipelines.

🧠 Per-space branding & workflows

Spaces become mini-apps: • /spaces/trips • /spaces/portfolio • /spaces/coop-home

Each space: • chooses its forms • chooses its templates • chooses its AI persona • chooses its pipeline

Eventually:

Your current “hardcoded coop mode” → just a Space

🧾 Documents become execution surfaces

Documents stop being “just text” and become: • Form results • Pricing outputs • AI-enhanced content • Export-ready artifacts (PDF, GDoc, FH, Shopify…)

Exactly like your Apps Script docs — but versioned, multi-user, branded.

3️⃣ Pre-populating Tally forms with structured data

Short answer

✅ Yes, partially ❌ Not full arbitrary prefill

What Tally supports (realistically)

Using:

Tally.openPopup(formId, { hiddenFields: { projectId, spaceId, userEmail, tripId, } })

You can: • pass IDs • pass context • pass references

You cannot reliably: • pre-fill visible fields arbitrarily • inject large JSON blobs • mutate form structure

So the correct pattern is:

Use Tally for input, not editing.

If data already exists → edit it in-app, not in Tally.

Recommended hybrid pattern

Stage Tool First intake Tally Draft editing In-app form / TripBuilder Refinement AI Output Document

This avoids fighting Tally’s limitations.

4️⃣ Can you generate / deploy Tally forms from your app?

Honest answer: ❌ Not really (and that’s okay)

As of now: • Tally does not expose a public form-creation API • You cannot programmatically deploy new forms

So building a “Tally form builder inside your app” is not realistic.

But here’s the smart workaround (very you)

🧠 Form Templates, not form creation

You store this:

type FormTemplate = { key: "trip-intake-v1"; description: string; expectedFields: string[]; recommendedTallyFormUrl: string; mappingJson: {}; };

In UI: • “Create Trip Intake” • → link to Tally template • → user duplicates it • → pastes formId back into your app

This is: • fast • realistic • scalable • delegation-friendly

5️⃣ Server actions & pipelines (where this shines)

Once a form is submitted, everything else is your code:

onSubmit → normalize → pipeline

Example pipelines: • trip-intake → create Document + TripDraft • lead-form → create Contact + CRM entry • partner-onboarding → create Project + Space

This is exactly what your old Sheets + AppScript did — just better.

6️⃣ How AI fits perfectly here

AI becomes: • Optional • Controlled • Context-aware

Examples: • “Write intro letter from form answers” • “Explain pricing options” • “Generate itinerary narrative”

AI never: • replaces forms • replaces pricing • replaces logic

This keeps your system robust and trustworthy.

7️⃣ Summary — the big picture

What you’re building is:

A configurable business OS where Projects & Spaces define: • how data enters (forms) • how it transforms (pipelines) • how it presents (documents/pages) • how smart it is (AI, optional)

Tally is just one input adapter — a very good one.

When you’re ready, the next good steps to generate together are: 1. FormConfig CRUD (server actions) 2. FormSectionRenderer 3. trip-intake-v1 pipeline action 4. Document template: trip-offer-v1 5. Space builder cloned from Project

Each can be done file-by-file, no rush.

You’re not overengineering — you’re productizing experience.

Perfect — your clarification is exactly right, and nothing you said contradicts the blueprint. What you’re really asking now is: 1. Trip documents stay special (and powerful) ✅ 2. Introduce one optional, generic entity that: • absorbs form-driven input (Tally or internal) • can power custom products / pages • does not hijack or complicate the core Trip / Document flow 3. Design it now, document it, then park it safely until you deliberately build it.

I’ll do this in three parts: 1. 🔒 Re-affirm the Trip Document exception (why it stays special) 2. 🧩 Design a Generic Intake Entity that fits your system 3. 🗂️ Document its full integration rules, so it never takes over

No code yet unless it helps clarity; this is architectural locking-in.

1️⃣ Trips remain a privileged document-driven entity (and should)

You are already doing the correct thing: • Trip is authored in a Document • The Document: • owns the editable structure • computes pricing • syncs LineItems • The Trip entity is: • generated / committed • editable later • stays in sync with its originating offer/document

This gives you something very rare and very strong:

A bidirectional relationship between Narrative (document) ↔ Operational data (trip)

⚠️ Important rule to keep (and you already follow it): • Trips are not form-first • They are document-first • Forms may seed a trip, but never replace the document

So: • ❌ Do NOT generalize Trips into a “generic form entity” • ❌ Do NOT let Tally create Trips directly • ✅ Tally → Intake → Draft → Document → Trip

This stays untouched. Good instinct.

2️⃣ The missing piece: a Generic Intake / Payload Entity

What you’re intuitively circling is not a “generic business entity” It’s a generic structured payload container.

Let’s name it properly so it doesn’t overreach:

📦 Submission (or Intake, or Payload)

I’ll use Submission below because it’s neutral and boring (that’s good).

3️⃣ What a Submission is (and is not)

✅ What it IS

A Submission is: • A typed, versioned, structured data blob • Created by: • Tally • Internal forms • API calls • Owned by: • a Project or • a Space • Designed to: • trigger pipelines • pre-fill documents • seed entities

❌ What it is NOT • Not a business entity (like Trip, Offer, Product) • Not editable like a document • Not responsible for pricing or logic • Not a replacement for Documents

Think of it as:

“What Google Forms + Sheets used to give you — but correctly modeled and local.”

4️⃣ Why this entity is worth having (but optional)

This one entity unlocks everything you want, without polluting Trips.

Examples of use • Trip intake form (Tally) • New Project onboarding • Lead capture • Custom product configuration • Partner submissions • Internal ops forms

But the Submission itself never “becomes” the product.

It always flows into something else.

5️⃣ Conceptual Prisma shape (for your notes)

Not for implementation yet — this is design locking.

model Submission { id Int @id @default(autoincrement()) type String // "trip-intake", "lead", "custom-product" source String // "tally", "internal", "api" sourceRef String? // tallyFormId, internal form key, etc

projectId Int? spaceId Int?

schemaKey String? // which schema this payload conforms to data Json // raw structured answers meta Json? // provider-specific stuff

status String @default("new") createdAt DateTime @default(now()) updatedAt DateTime @updatedAt }

⚠️ Notice: • No business logic • No assumptions • Pure payload + metadata

6️⃣ How this integrates cleanly with your existing framework

🔗 With Projects • Project can define: • accepted submission types • pipelines that consume them • Example: • trip-intake-v1 pipeline listens to Submission[type="trip-intake"]

🔗 With Spaces (Pages) • Space section can render: • a Tally form • an internal form • On submit: • create Submission • optionally trigger pipeline • Space never “knows” what happens next

🔗 With Documents

This is the important part for you: • A Document can: • reference a Submission • pull its data • transform it into narrative • This replaces your old: • Sheet → Apps Script → Google Doc

Now it’s:

Submission → Document → (Trip / Offer / Anything)

7️⃣ Tally integration — how it fits without taking over

Your instincts are correct: Tally is just one adapter.

How it plugs in • User pastes: • Tally Form ID or link • You store it in: • FormConfig (or directly on Space section) • When opened: • Tally opens with hidden fields: • projectId • spaceId • userId (optional)

On submit: • Payload → /api/submission/tally • Server: • validates • stores Submission • optionally triggers pipeline

Tally never: • touches Documents • touches Trips • touches pricing

8️⃣ The “generic entity template” idea — refined properly

You were right to hesitate, so here’s the safe version:

❌ Not:

“Users create arbitrary entities”

✅ Instead:

Users define Submission Schemas

These schemas: • describe fields • validation • intended pipeline

Example schema keys: • trip-intake-v1 • custom-product-config • lead-basic • event-registration

This gives them: • flexibility • power • zero architectural chaos

9️⃣ Why this will not take over your system

Three guardrails (write these down): 1. Submissions never render themselves 2. Submissions never contain logic 3. Only pipelines create real entities

If you keep those rules, this feature stays contained forever.

🔟 How this complements your Trip super-document (perfectly)

Your Trip flow becomes: 1. Submission (optional) 2. Document (authoritative) 3. Trip entity (operational) 4. LineItems (financial) 5. Sync / edit / export

Your existing work: • ✅ already supports steps 2–5 • Submission simply replaces Sheets + Apps Script

11️⃣ Recommendation: how to park this safely

Create a file called something like:

/docs/architecture/SubmissionEntity.md

Paste: • the intent • the Prisma sketch • the guardrails • the integration points

Then do nothing until you choose a week to build it.

It will still be correct when you come back.

If you want, next time we can: • turn this into a one-page internal spec • or generate exact Prisma + API routes • or wire Submission → Document auto-draft • or map Submission → Trip initializer

Short answer: yes — absolutely, and it’s a very good theoretical direction, if it’s framed correctly.

Long answer below, carefully scoped so this stays a design exercise, not a commitment 👇

🧠 Theoretical framing (important)

What you’re describing is not classical inheritance in the ORM sense.

It’s closer to:

A base “Submission” envelope • a small, curated set of semantic extensions that inform how the system may interpret, route, or enrich the data.

Think protocols / interfaces, not subclasses.

This distinction is why it stays sane.

1️⃣ The right mental model: Envelope + Semantic Lens

Base type (always the same)

Submission ├─ id ├─ type // semantic intent, not structure ├─ source // tally | internal | api ├─ data // raw answers ├─ meta // provider / transport info ├─ context // projectId | spaceId | userId └─ status

This never changes.

“Extensions” are interpretation contracts

Instead of: • TripSubmission extends Submission • LeadSubmission extends Submission

You get: • Submission + TripIntent • Submission + LeadIntent

Each intent answers: • What kind of thing might this become? • Which tools are allowed to touch it? • Which pipelines may consume it? • Which documents can be prefilled from it?

2️⃣ What your “4–5 models” really are

They are Intent Profiles, not models.

For example:

🔹 Intent: INTAKE_TRIP • Allowed outputs: • Trip Draft • Offer Document • Allowed enrichments: • Pricing engine • City resolution • Disallowed: • Direct entity creation • Typical sources: • Tally • Public form • Default document template: • trip-offer-v1

🔹 Intent: INTAKE_LEAD • Allowed outputs: • Contact • CRM entry • Allowed enrichments: • AI summary • Typical sources: • Landing page • Popup • No documents by default

🔹 Intent: CONTENT_REQUEST • Allowed outputs: • Document section • Draft page content • AI-first • Often space-bound

🔹 Intent: CUSTOM_CONFIG • Allowed outputs: • Product config • Quote request • Strict validation • Strong schema coupling

🔹 Intent: OPS_REQUEST • Internal use • Pipelines only • No UI exposure

These are the 4–5 “models” you’re intuitively imagining — but notice:

👉 They don’t change the database shape 👉 They change what the system is allowed to do next

3️⃣ How this fits perfectly with Tally (theoretically)

Tally already gives you: • arbitrary fields • conditional logic • validation • hidden fields

So the integration becomes:

User flow 1. User creates a Tally form (their own) 2. In your Space section, they select: • Form source: Tally • Intent: INTAKE_TRIP 3. On submit: • You store raw payload • Attach intent metadata

{ "type": "submission", "intent": "INTAKE_TRIP", "source": "tally", "data": { "...raw form answers..." } }

No schema lock-in. No hard coupling. No duplication.

4️⃣ How “extensions” integrate with Documents & Trips

This is where your system becomes unusually powerful.

Example: Trip intake

Submission arrives with: • intent: INTAKE_TRIP

That intent unlocks: • ✅ “Create Trip Document from this” • ✅ “Prefill TripBuilder section” • ✅ “Run pricing suggestions” • ❌ “Directly create Trip entity”

Later: • Document commits → Trip entity • Submission remains immutable history

You’ve preserved: • audit trail • explainability • reversibility

5️⃣ Why this doesn’t turn into a generic monster

Because you curate intents.

Users: • choose from existing intents • cannot invent new system behaviors • only control input structure

You control: • what each intent unlocks • what it may produce • which entities it may touch

This aligns perfectly with your coop / OS philosophy.

6️⃣ Theoretical UI implication (for Spaces)

When a user adds a section like:

“Accept input”

They choose: 1. Input source • Internal form • Tally form 2. Intent • Trip Intake • Lead • Custom Config 3. Post-submit action • Save only • Open document draft • Trigger pipeline

The section itself stays dumb. The intelligence lives in the intent.

7️⃣ Why this is a research-worthy idea (not just theoretical fluff)

This pattern is: • used in large workflow engines • used in event-driven systems • compatible with AI mediation • explainable to humans

It also: • keeps your Prisma clean • keeps Trips sacred • keeps Documents central • lets Tally be first-class without dominance

8️⃣ Bottom line (design verdict)

✅ Yes — as a base type with semantic extensions, not ORM inheritance ✅ Yes — curated, not user-defined behaviors ✅ Yes — perfectly aligned with your philosophy ⚠️ Only if you keep: • Submissions dumb • Intents finite • Documents authoritative

You don’t need to build this now — but you’ve correctly discovered a pattern that scales.

If you want next time, we can: • formalize the intent registry • sketch the exact UI selector • or map intents → allowed actions table

Just say the word.

Yes — and more than that: what you just described is the natural convergence point of everything you’ve built so far. Not a stretch, not a fantasy, not “AI hype” — it’s a very clean system once named properly.

Let me anchor it clearly, then give you a concrete, buildable mental model you can keep and reuse.

🧭 The Core Idea (stated plainly)

Any surface → one capture window → one Submission → structured entities + documents + actions

This is the Apple Notes → Things → Notion → CRM unification you’re intuitively building.

You already have: • Documents (rich, editable, persistent) • Trip builder (structured, priced, syncable) • AI personas & contexts • Entity builders (Projects, Spaces) • Pipelines (LineItems, Trip ↔ Document)

You’re missing only one thin layer: 👉 Universal Capture → Submission

🧩 Canonical Flow (this is the keeper)

[Any Surface] ├─ Document button ├─ Page button ├─ Floating quick capture ├─ Chat paste ├─ Tally (later) ▼ [Quick Capture Window] ├─ Notes (free text / paste) ├─ Entity selector (Trip / Project / Space / Generic) ├─ Optional hints (dates, pax, city, etc.) ▼ [Submission] ├─ raw notes ├─ structured hints ├─ source ├─ intent ▼ [AI Enrichment (optional)] ├─ infer fields ├─ create draft ├─ validate / normalize ▼ [Draft Entity] ├─ Trip (draft) ├─ Document (draft) ├─ Project / Space ▼ [Dashboard] ├─ Inspect ├─ Edit ├─ Publish ├─ Export

This is exactly what you described — and it is coherent.

✨ Why this works so well

1️⃣ One capture surface solves many problems

Instead of: • separate forms • separate flows • separate logic

You get: • one capture window • one pipeline • one mental model

Apple Notes-style capture, but business-aware.

2️⃣ Pasting WhatsApp / email is a feature, not a hack

This is huge and you spotted it instinctively.

Most real-world work starts as messy text.

WhatsApp message like:

“Hi, family of 4, Granada–Cordoba–Seville, early April, mid-range hotels, maybe a guide in Granada…”

That is perfect Submission input.

You don’t want a form there. You want capture + AI normalization.

3️⃣ Entity selection gives direction, not rigidity

The key is:

User chooses intent, not structure.

Example: • “This is a Trip” • “This is a Project idea” • “This is just a Note”

Everything else can be inferred or refined later.

🧠 AI’s role (kept sane)

AI is not deciding everything.

It: • extracts • suggests • drafts • pre-fills

Human: • inspects • corrects • approves • publishes

This keeps trust and reliability.

📦 What “ready” means in your dashboard

When you say:

“go to our dashboard and find the trip ready”

That means: • Trip exists • In draft or review state • Has: • cities • dates • pax • pricing (optional) • Linked: • to a document • to its submission • to its project/space (optional)

From there, your existing menus already work: • Share • Export • Publish • Turn into Space • Seasonal offer • Event

You’re not inventing new actions — you’re feeding the ones you already have.

🧱 How this fits Projects & Spaces perfectly

Projects • long-running initiatives • operational • internal dashboards

Spaces • public-facing • themed • brand / portfolio / trip showcase

Submissions • entry points • raw intent • pre-entity

This triad is extremely strong:

Submission → Entity (Trip) → Space (Public Page) ↓ Document

🔁 This also scales down beautifully

You can start with: • One “➕ Quick Capture” button • Modal • Textarea + entity selector • Server action → create Submission • Button: “Generate Draft”

That’s it.

No Tally. No builders. No overengineering.

🧪 Later enhancements (optional, natural) • Voice input • Email forwarding → Submission • WhatsApp webhook → Submission • Tally → Submission • AI auto-tagging • “Continue from last Submission”

None of these change the core.

🏁 Final confirmation (important)

What you described is: • architecturally clean • product-worthy • portfolio-grade • delegation-friendly • and very real-world useful

It’s not “yet another feature” — it’s the missing connective tissue.

If you want, next time we can: • name this module properly • sketch the Submission UI • define minimal schema • or design the “Generate Trip Draft” step

But as an idea?

✅ Yes — this is the right system. You’re not imagining it.