H1SKILL: Document Engine (Virtual Instrument)
Module: Document System Status: Active / Stable enough for continued work Part of: MASTER_SKILL.md — Chapter 4 See also:
./mlv-document-one_akill.md,./GOALS-AHEAD.md
H2Identity
The Virtual Document Instrument is an in-app entity — not a file, not a Google Doc.
It is:
- An internal application entity, editable in the web app
- Rendered through Folio (pagination + wagons)
- Used as orchestration surface (triggers real operations)
- Used as projection surface (reflects entity state)
- Used as commercial instrument (offers, proposals, letters)
- A trigger surface for exports and workflows
It is NOT:
- A Google Doc
- A DOCX file
- The canonical truth for trips, offers, orders, or payments
H2Document Templates Registry
// app/(pages)/documents2/components/DocRegistryComponents.tsx
Templates:
note2 → free-form notes
offer → commercial offer (local or bound to Offer entity)
proforma → pre-invoice
invoice → final invoice
firmorder → confirmed order document
trip-offer-v1 → trip commercial proposal (primary)
custom → open template
Each template shape:
{
key: string,
label: string,
emoji: string,
requireExplicitEdit: boolean,
defaultData: Record<string, any>,
renderView(data): JSX.Element,
renderEditor(data, update): JSX.Element
}
H2Document Data Model
All templates share a core JSON structure stored in projectDocument.data:
{
title: string,
description?: string,
clientName?: string,
meta: { currency?: string, [k: string]: any },
// Offer linking:
offerId?: number | null,
offerMode?: "local" | "bound",
// Line items (local mode):
lineItemsDraft?: LineItem[],
// Trip section (trip-offer-v1):
tripSection?: {
kind: "trip",
tripData: TripDraftData
},
// Content sections:
contentSections?: Array<{
id: string,
type: "text",
role: "letter-intro" | "itinerary" | "cta" | string,
content: string
}>,
// CTA:
ctaSection?: {
enabled: boolean,
type: "whatsapp" | "email" | "link",
phone?: string,
text: string
}
}
H2Offer Mode Rules
"local" mode:
- Document stores its own draft line items in
lineItemsDraft - User can freely edit line items
- Can trigger
createOfferFromDocument()to promote to a real Offer
"bound" mode:
- Document reflects real Offer line items via
getOfferWithLineItems(offerId) - Line items are read-only in the document
- Editing happens through the Offer editor →
updateOfferLineItemsFromEditor() - Can detach with
detachDocumentFromOffer()→ copies canonical items into local mode
H2Document Actions Reference
// Offer linkage
getOfferWithLineItems(offerId)
→ { items: LineItem[], totals: { subtotal, tax, total } }
createOfferFromDocument(documentId)
→ creates Offer from local draft items → binds document → returns { offerId }
bindDocumentToOffer(documentId, offerId)
→ attaches document to existing offer → switches to bound mode
detachDocumentFromOffer(documentId)
→ copies canonical offer items into document draft → switches to local mode
updateOfferLineItemsFromEditor(offerId, items[])
→ canonical editing API for bound offer line items
// Trip integration
commitTripFromDocument({ documentId, tripDraft })
→ persists TripDraftData as real Trip entity → returns { tripId }
syncTripLineItems({ tripId, items })
→ upserts line items by (tripId, key) → deletes orphaned items
populateTripOfferFromTrip({ trip, pricing, client })
→ deterministic doc generation → fills contentSections
H2Intent Flag Pattern (Command-Driven)
DocSection_TripBuilder emits intents, never side effects:
// Button in TripBuilder:
onChange?.({
...section,
tripData: {
...trip,
_requestCommitTrip: true, // persist trip to DB
_requestInsertIntoLineItems: true, // insert line items
_requestOverwriteTripLineItems: true, // overwrite line items
_requestPopulateTripOffer: true, // fill document content sections
},
});
Documents2Canvas.handleTripSectionChange is the only coordination point:
- Reads intent flags
- Calls the appropriate server action
- Queues save with flag reset to
false - Returns early after handling
H2Document Lifecycle
Status-driven (never deleted):
Draft → Proposed → Confirmed
Only Orders are permanent records.
Documents, Offers, Trips are revisable.
Revisions create new versions, not silent overwrites.
H2Folio (Render Layer)
rawBlocks (input)
↓ normalize
FlowUnits
↓ paginate (layout only)
wagons (render units per page)
↓
DOM output
Invariants:
- Deterministic IDs across renders
- Pagination is layout only — never business logic
- Pure transforms — no side effects in render pipeline
H2What this is NOT
- Not a Google Doc editor
- Not the source of truth for business entities
- Not responsible for payment, booking, or order truth
- Not a file system
This skill is Chapter 4 of MASTER_SKILL.md.
See also: ./GOALS-AHEAD.md for full Document Engine v2 + Offer/Order spec.