Add Document Generator SDK app + step-by-step tutorial (#22522)
## What & why
This adds a **guided tutorial** that teaches the Twenty SDK by building
one real, useful app end to end — plus the finished app itself, ready
for the marketplace.
The app, **Document Generator**, turns reusable templates into
personalized documents using CRM data: write a template once with
`{{placeholders}}`, then generate a filled-in document for any Person or
Company from the command menu, an AI agent, or a workflow.
## Two parts
**1. The app — `packages/twenty-apps/public/document-generator`**
Each capability maps to one tutorial chapter:
- **Data:** `documentTemplate` + `document` objects, fields, and a
bidirectional relation
- **Logic:** a single `generate-document` handler exposed as an **AI
tool**, a **workflow action**, and an **HTTP POST route**; plus a public
**HTML view route**
- **UI:** two views + sidebar navigation, a **command-menu item** (on
Person selection) that opens a **React front component**
- **AI:** an agent + skill; a default application role; marketplace
metadata + logo
- **Tests:** unit tests for the template renderer + an install
integration test
**2. The tutorial —
`packages/twenty-docs/.../apps/tutorials/document-generator/`**
A six-chapter series under **Developers › Apps › Tutorial** (Overview →
Data model → Generating documents → HTTP routes → Building the UI → AI
agent → Publishing). Minimal prose, paste-ready code, inline links to
the matching reference pages, and real screenshots. Registers a new
"Tutorial" nav group and regenerates `docs.json` + the navigation
template.
## Verification
Validated against a running Twenty instance (`twenty-app-dev` on
`:2020`):
- `twenty dev --once` installs cleanly (28 metadata objects created)
- Generated a real document from a Person — placeholders resolved (name,
job title, `company.name`, email), zero missing tokens
- Command menu → front component → generate flow works in the UI
- Public HTML view route renders the document
- App gates green: `yarn lint` (0/0), `yarn typecheck`, `yarn test:unit`
(7/7)
All screenshots in the tutorial are captured from this run.
## Notes
- Left out per-app CI workflows (`.github/workflows`) to keep scope
tight — happy to add them if wanted.
https://claude.ai/code/session_012FoC76zPbPmpgtN7MXMPwy
---
_Generated by [Claude
Code](https://claude.ai/code/session_012FoC76zPbPmpgtN7MXMPwy)_
<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/twentyhq/twenty/pull/22522?utm_source=github"
target="_blank" rel="noopener noreferrer"
data-no-image-dialog="true"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img
alt="Review in cubic"
src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a>
<!-- End of auto-generated description by cubic. -->
---------
Co-authored-by: github-actions <github-actions@twenty.com>
|
After Width: | Height: | Size: 258 KiB |
|
After Width: | Height: | Size: 114 KiB |
|
After Width: | Height: | Size: 193 KiB |
|
After Width: | Height: | Size: 119 KiB |
|
After Width: | Height: | Size: 200 KiB |
|
After Width: | Height: | Size: 679 KiB |
|
After Width: | Height: | Size: 579 KiB |
|
After Width: | Height: | Size: 124 KiB |
|
After Width: | Height: | Size: 183 KiB |
|
After Width: | Height: | Size: 218 KiB |
|
After Width: | Height: | Size: 238 KiB |
|
After Width: | Height: | Size: 343 KiB |
@@ -0,0 +1,76 @@
|
||||
<svg width="900" height="380" viewBox="0 0 900 380" fill="none" xmlns="http://www.w3.org/2000/svg" font-family="-apple-system, BlinkMacSystemFont, 'Segoe UI', Inter, Roboto, sans-serif">
|
||||
<defs>
|
||||
<linearGradient id="band" x1="0" y1="0" x2="1" y2="0">
|
||||
<stop offset="0" stop-color="#1961ED"/>
|
||||
<stop offset="1" stop-color="#6B9BFF"/>
|
||||
</linearGradient>
|
||||
<filter id="shadow" x="-20%" y="-20%" width="140%" height="150%">
|
||||
<feDropShadow dx="0" dy="6" stdDeviation="10" flood-color="#18274B" flood-opacity="0.10"/>
|
||||
</filter>
|
||||
</defs>
|
||||
|
||||
<!-- Template card -->
|
||||
<g filter="url(#shadow)">
|
||||
<rect x="40" y="70" width="200" height="180" rx="14" fill="#FFFFFF" stroke="#E4E9F4"/>
|
||||
<rect x="40" y="70" width="200" height="8" rx="4" fill="#C4A2E0"/>
|
||||
<text x="64" y="112" font-size="15" font-weight="700" fill="#10152A">Template</text>
|
||||
<text x="64" y="140" font-size="12" fill="#7A46C6" font-family="monospace">Dear {{firstName}},</text>
|
||||
<rect x="64" y="152" width="150" height="9" rx="4.5" fill="#EEF1F6"/>
|
||||
<rect x="64" y="170" width="120" height="9" rx="4.5" fill="#EEF1F6"/>
|
||||
<text x="64" y="200" font-size="12" fill="#7A46C6" font-family="monospace">{{company.name}}</text>
|
||||
<rect x="64" y="212" width="140" height="9" rx="4.5" fill="#EEF1F6"/>
|
||||
</g>
|
||||
<text x="140" y="278" font-size="12.5" fill="#6B7280" text-anchor="middle">Write once, with placeholders</text>
|
||||
|
||||
<!-- Generate hub -->
|
||||
<g>
|
||||
<circle cx="450" cy="160" r="46" fill="#1961ED"/>
|
||||
<circle cx="450" cy="160" r="46" fill="none" stroke="#1961ED" stroke-opacity="0.25" stroke-width="10"/>
|
||||
<path d="M436 160l9 9 18-19" stroke="#FFFFFF" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<text x="450" y="230" font-size="14" font-weight="700" fill="#10152A" text-anchor="middle">Generate</text>
|
||||
</g>
|
||||
|
||||
<!-- arrows -->
|
||||
<path d="M248 160H392" stroke="#C4C9D6" stroke-width="2.5" stroke-linecap="round"/>
|
||||
<path d="M384 154l10 6-10 6" fill="#C4C9D6"/>
|
||||
<path d="M504 160H648" stroke="#C4C9D6" stroke-width="2.5" stroke-linecap="round"/>
|
||||
<path d="M640 154l10 6-10 6" fill="#C4C9D6"/>
|
||||
|
||||
<!-- Document card -->
|
||||
<g filter="url(#shadow)">
|
||||
<rect x="660" y="60" width="200" height="200" rx="14" fill="#FFFFFF" stroke="#E4E9F4"/>
|
||||
<rect x="660" y="60" width="200" height="8" rx="4" fill="url(#band)"/>
|
||||
<text x="684" y="102" font-size="15" font-weight="700" fill="#10152A">Dear Ada,</text>
|
||||
<rect x="684" y="116" width="150" height="9" rx="4.5" fill="#EEF1F6"/>
|
||||
<rect x="684" y="134" width="150" height="9" rx="4.5" fill="#EEF1F6"/>
|
||||
<rect x="684" y="152" width="110" height="9" rx="4.5" fill="#EEF1F6"/>
|
||||
<rect x="684" y="178" width="60" height="9" rx="4.5" fill="#EEF1F6"/>
|
||||
<rect x="684" y="196" width="140" height="9" rx="4.5" fill="#EEF1F6"/>
|
||||
<g>
|
||||
<rect x="756" y="216" width="80" height="26" rx="13" fill="#FDECEC"/>
|
||||
<text x="796" y="233" font-size="12" font-weight="700" fill="#D6455D" text-anchor="middle">PDF file</text>
|
||||
</g>
|
||||
</g>
|
||||
<text x="760" y="288" font-size="12.5" fill="#6B7280" text-anchor="middle">A polished, shareable document</text>
|
||||
|
||||
<!-- Trigger pills -->
|
||||
<text x="450" y="312" font-size="12.5" fill="#6B7280" text-anchor="middle">Trigger it from anywhere</text>
|
||||
<g font-size="12.5" font-weight="600" fill="#3A4256">
|
||||
<g>
|
||||
<rect x="196" y="330" width="120" height="34" rx="17" fill="#F4F6FB" stroke="#E4E9F4"/>
|
||||
<text x="256" y="352" text-anchor="middle">Command menu</text>
|
||||
</g>
|
||||
<g>
|
||||
<rect x="330" y="330" width="92" height="34" rx="17" fill="#F4F6FB" stroke="#E4E9F4"/>
|
||||
<text x="376" y="352" text-anchor="middle">AI agent</text>
|
||||
</g>
|
||||
<g>
|
||||
<rect x="436" y="330" width="128" height="34" rx="17" fill="#F4F6FB" stroke="#E4E9F4"/>
|
||||
<text x="500" y="352" text-anchor="middle">Workflow step</text>
|
||||
</g>
|
||||
<g>
|
||||
<rect x="578" y="330" width="126" height="34" rx="17" fill="#F4F6FB" stroke="#E4E9F4"/>
|
||||
<text x="641" y="352" text-anchor="middle">Shareable link</text>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 4.0 KiB |