This document captures the purpose, structure and architectural decisions for the Lolly platform. It reflects both the product vision and the current state of the codebase.
Status: Lolly is an internal prototype in a closed pilot that hasn't completed. The engine is deterministic and internally consistent, but the product is early - SUSE is customer number one - and its cryptography and file-parsing engines are currently undergoing SUSE's strict infrastructure hardening, preparing for enterprise scale (we're really good at this). Read the architecture below as design intent under test, not a finished, certified product. See Adoption & Governance for how the pilot is run and measured.
How to read this page. It carries two kinds of material, in order. The first half is why this exists: the problem, the positioning and the lifecycle a single asset travels through. From The big picture onward it is how the layers fit: the architecture document for contributors, covering the engine/shell/pack separation, the repository layout, the delivery targets and the commitments that constrain every change to the platform. If you are here to change the codebase rather than to understand the product, start at the big picture.
Two companions go deeper than this page does.
engine/README.mdin the repository is the module-by-module map of the engine, with a generated table of every module and what it parses or writes. Threat Model & Trust Boundaries is the same architecture read as trust boundaries, and it is the right page for any question about what the engine treats as untrusted.
Why this exists
Repeatable creative work is too predictable to justify skilled hands every time, and too quality-sensitive to hand off without guardrails. Left alone it goes one of three ways: slow, inconsistent, or locked into a vendor's templates.
This platform is the direct answer:
Programmatic creative and content at scale - assets generated from inputs, with the rules set once, for whoever needs them.
Lolly lets you build or import a design system, author a layout and turn that work into repeatable production. A tool takes inputs and produces a finished asset, using the same engine across the browser, desktop and CLI.
The outcome is abundance: every event has correct signage, every CVE alert matches the house style, every label prints clean, every email signature is current - all from tools that already carry the rules. Designers can create original work on the Design canvas and publish reusable tools for recurring production.
Innovate probabilistically, scale deterministically
Every argument about AI in a creative pipeline stalls on the same question: which part of this is the machine's job? It is an old question with a settled answer. Scribes and illuminators already worked between two instruments - the loose sketch, where nothing was fixed and everything could be tried, and the printing press, intimidating precisely because it committed. The sketches were where the art happened. The press was how it reached anyone. Nobody confused the two, and both kept advancing - new inks, new faces, new presses - each improving in harmony with the craft and the intention it served.
Lolly draws the same line. Explore probabilistically: a model, a designer, a rough idea, a prompt that goes somewhere nobody planned. Then scale deterministically - the thing that becomes many outputs is a tool, and a tool renders the same way every time from inputs you can read. The exploration stays free because nothing downstream depends on it landing the same way twice. The output earns trust because it is not a guess. Getting AI experimentation into predictable, reproducible outcomes is not a new discipline; it is the same division of labour that made printed work worth trusting in the first place.
Trust the creative process, scale with rigour.
Against the alternatives
Lolly includes lolly.work, the same project's separately deployed organisation service. Local creation needs no account; shared work and server rendering use the configured instance. Competitor scores: August 2026. Lolly coverage: 28 September 2026.
| Capability | Lolly+ lolly.work | Penpotopen design | CanvaAffinity Cavalry |
Adobedesktop pro | Brand DAMFrontify Bynder |
Cloudinarymedia pipeline | Figmaonline pro | Render APIsBannerbear Placid Creatomate |
|---|---|---|---|---|---|---|---|---|
| Overall completenessunweighted mean - weight rows for your own context · columns are sorted by this row | 84 | 70 | 57 | 48 | 41 | 41 | 41 | 34 |
Production maturity & track recordCanva, Adobe, Figma, Bynder/Frontify: a decade-plus at massive scale. Render APIs: years in production pipelines. Penpot: shipping, large community, younger at enterprise scale. Lolly: closed pilot, security hardening underway, no public case studies. Cloudinary: 2012, enterprise media infrastructure at global scale. |
25 | 75 | 100 | 100 | 100 | 100 | 100 | 75 |
Mass generation from data (CSV / API)Lolly: batch grid and CLI, plus lolly.work's recoverable server jobs and batches with retries, cancellation and retained outputs. Canva: Bulk Create + Autofill API - real, but Enterprise-gated, async, text/image fields only; Affinity Publisher adds desktop data merge. Adobe: InDesign data merge and scripting. Figma: Buzz fills templates from CSV or XLSX, free in beta (Aug 2026) - cloud- and account-gated, 50. Penpot: open API/MCP, not purpose-built. Render APIs: this is their entire product - account- and cloud-gated, so 75 under the method rule. DAM: Studio-style batch create and resize. Cloudinary: URL-driven overlays and named transforms derive variants at scale - account-gated, 75. |
100 | 50 | 50 | 50 | 75 | 75 | 50 | 75 |
Offline & air-gap operationLolly: standalone creation works offline once its assets are available; lolly.work's organisation services need a reachable instance, which can run on an internal network. The Canva column is scored on its best family member: Affinity (free since Oct 2025) runs offline as a desktop suite once a verified account activates it - 75, the same deduction Adobe takes. Canva's own Offline (2026) still edits pre-synced designs only, per device, in a 14-day window. Adobe: desktop apps run offline; licensing phones home. Figma: cached-file viewing and limited editing. Penpot: self-host on your infra behind a firewall (server required). Cloud APIs and DAM: none. |
100 | 75 | 75 | 75 | 0 | 0 | 25 | 0 |
On-device rendering - data never leavesLolly: local rendering and conversion in-browser or CLI need no upload. Choosing lolly.work's shared storage, work collabs or server rendering sends the relevant data to the configured instance. Adobe: local desktop rendering, but cloud services and telemetry in the suite. Figma: canvas renders locally, files live in Figma's cloud. Penpot: 90 - rendering happens in the browser and the save target is a server that can be your own sovereign private cloud, even your own laptop, with private export throughout; only the server hop separates it from Lolly. Canva itself is server-side by design; the column's 75 is Affinity - local desktop rendering, account activation and telemetry, the same shape as Adobe. Render APIs, DAM: server-side by design. |
100 | 90 | 75 | 75 | 0 | 0 | 25 | 0 |
Hard brand constraints (structural)Lolly: templates fix layout rules and expose permitted inputs. lolly.work adds group access, input policies, shared catalogues, approvals and audit records. Copy and usage rights can still need review. Canva: element locks - permission-based, admin-set and static: no template logic such as conditional logo switching or responsive recomposition at fill time, and Canva AI does not yet respect Brand Controls - 50. Render APIs: templates are fixed (only declared fields vary) but no governance layer. DAM: locked templates with restrictions. Figma: Buzz templates hold locked guidelines at fill time; the design surface stays review-enforced - 50. Penpot: tokens and systems enforced by review, not runtime. Adobe: libraries, little enforcement. Cloudinary: presets and Enterprise-gated transformation templates fix operations, not brand layout - 50. |
100 | 50 | 50 | 25 | 75 | 50 | 50 | 75 |
C2PA provenance, signed at creationAdobe: the broadest shipped implementation (Photoshop, Lightroom, Premiere, Firefly) - signing happens locally in the desktop apps and in the cloud for the Content Authenticity web app, and nothing signs without an Adobe account and an Adobe-provisioned identity, so it is account-gated end to end: 75 under the method rule. Lolly: optional on-device signing without an account, plus lolly.work server signing with a configured organisation identity. A verifier must trust the signer's root to show a trusted identity. The stack remains pilot-stage, so 75. Penpot: 50 via the official Lolly Export plugin - the same on-device engine signing Lolly uses, opt-in, as in Lolly (and disclosed plainly: it is Lolly's own plugin). No evidence of C2PA writing in Canva, Figma, render APIs or DAM as of Aug 2026. Cloudinary: 50 - signs images on delivery (fl_c2pa, CAI member since 2020), attesting delivery by Cloudinary rather than creation by you. |
75 | 50 | 0 | 75 | 0 | 50 | 0 | 0 |
CLI, pipeline & AI-agent automationLolly: CLI, TUI, MCP server and URL mode, plus lolly.work's authenticated render API, recoverable batches and governed delivery. One engine across the local and server paths. Render APIs: API-first, webhooks, integrations - account- and cloud-gated, so 75 under the method rule. Penpot: open API, plugins, official MCP server. Canva: Connect API - Enterprise-gated, async, rate-limited. Adobe: UXP/ExtendScript plus Firefly APIs. Figma: strong REST/plugin API, no render pipeline. DAM: asset-management APIs. Cloudinary: API-first media pipeline - account-gated, 75. |
100 | 75 | 50 | 50 | 50 | 75 | 50 | 75 |
Live collaboration (real-time co-editing)Figma: the scale benchmark - 200 simultaneous editors, up to 500 people in a file - account and cloud required. Canva: mature real-time editing across the product, same precondition. Penpot: real-time multiplayer with live cursors; self-hosting lifts the vendor-account precondition, but a server is still required. Lolly: direct two-device private collabs without an account, plus authenticated work collabs through lolly.work with shared edits, cursors and saved team sessions. Partial reflects the current single-node work deployment and limited operating history. Private collabs work without internet on a shared network; work collabs need the organisation's instance. Adobe: Live Co-Editing in beta, cloud documents only. Render APIs: no editing surface. DAM: comments and approvals, not canvas co-editing. Cloudinary: DAM comments and workflows, no canvas co-editing. |
50 | 75 | 75 | 25 | 25 | 25 | 75 | 0 |
No design skill requiredCanva: the category benchmark for ease. DAM: fill a locked template. Lolly: fill in fields using an existing tool, or publish a tool from Design through Share with rules. lolly.work manages access and input policy for the team. Render APIs: swaps design skill for developer skill. Adobe, Figma, Penpot: professional tools. Cloudinary: the media library is easy; transformations are developer territory. |
100 | 25 | 100 | 25 | 100 | 50 | 25 | 50 |
Open source, self-hosted, no lock-inPenpot: MPL-licensed, 51k+ stars, self-host via Docker/K8s - fully verifiable today. Lolly and lolly.work: MPL-2.0 open source in separate repositories, both self-hostable. lolly.work provides the organisation services as part of the project. The current 75 reflects the young project and limited deployment history. Everything else is proprietary SaaS or proprietary desktop. |
75 | 100 | 0 | 0 | 0 | 0 | 0 | 0 |
Cost at production scaleLolly + lolly.work and Penpot: no per-seat software licence charge for the open-source self-hosted path; hardware, storage and operations remain costs. Render APIs: metered - roughly $0.005–0.049 per image, credits often expiring monthly. Canva/Figma: per-seat; Canva's automation needs Enterprise, though Affinity itself is free since Oct 2025. Adobe: premium per-seat suite. DAM: sales-led enterprise quotes, MAU or seat models. Cloudinary: credit-metered across transforms, storage and bandwidth - 25. |
100 | 100 | 50 | 25 | 25 | 25 | 50 | 25 |
Annual medium enterprise spendIndicative list-price arithmetic for a medium enterprise - think 500-5000 people - August 2026: a guess range, not a quote; deals vary and the seat mix dominates. Ring = how much of the board's largest annual spend (US$500k) you keep; the number in the ring is each range's upper bound. Canva (US$75k-400k; Affinity free since Oct 2025 pulls the design-seat share down), Adobe (US$100k-500k) and Figma (US$30k-250k) assume the realistic creative-seat share of an organisation this size - tens to hundreds of seats, not a licence for every employee. Render APIs price by usage (US$5k-60k). DAM is quote-only, commonly US$50k-250k and up. Penpot is hand-set at 99: "your server" can be your own laptop - a small technical hurdle at well under 1% of an Adobe-scale spend. Lolly + lolly.work: US$0 in software licence charges for self-hosting. The figure excludes devices, organisation hosting, storage, support and administration; these need a separate budget. Standalone Lolly does not need the organisation service. Cloudinary: US$30k-150k, credit-based; enterprise contracts commonly sit near US$82k. |
$0 | $0 | $400k | $500k | $250k+ | $150k | $250k | $60k |
Capability comparison: competitor research and scores from August 2026; Lolly + lolly.work coverage reviewed 28 September 2026. Scoring: 0 absent, 25 workaround-grade, 50 real but gated or partial, 75 strong with caveats, 100 core competency.
signed by Lollypositioning-comparison.htmlHTMLAI generatedgenerated by GPT-6Check it yourselfGet the signed filepixels, not shapes39 KBThe comparison covers Lolly + lolly.work. Lolly provides local creation, the Design canvas and reusable tools. lolly.work is the same project's separately deployed organisation service: SSO, SCIM, roles, shared catalogues and projects, approvals, work collaboration, usage reporting, audit records and server rendering. Both parts are MPL-2.0 open source. Standalone creation needs no account; shared work uses the organisation's configured services. See the comparison overview for the split and the brand-portal comparison for the governance capabilities.
signed by Lollyvector SVGCheck it yourselfGet the signed file384 paths~34k nodes795 groups4 images1,652 KB
signed by Lollyvector SVGCheck it yourselfGet the signed file384 paths~34k nodes795 groups4 images1,649 KB
Use it for: Rapid generation of operationalised creative assets - event tiles, name badges, signatures, CVE alerts, QR codes, social cards, consignment labels, structured reports.
Choosing an editor: Use Design for original layouts and reusable tools, or import work from another application. Specialist retouching, illustration, prototyping and video requirements can determine which authoring application a designer chooses.
The lifecycle of a campaign
The clearest way to see what Lolly is isn't a feature list - it's to follow a single asset as it passes from hand to hand. Watch one localized campaign card move through the organisation:
- The creative sets the rules. A designer authors the base template in the Design tool, hard-coding the brand's typography and colour variables. They're not making one card - they're doing the foundational work once so they never have to hand-localize it again.
- The developer scales it. That same template is wired into a nightly pipeline through the CLI, so a fresh chart or a new language variant is generated automatically - no designer re-opens the file.
- The producer just uses it. A sales rep, offline on a plane, opens the same tool and generates a perfectly on-brand deck for a client meeting. No design skill, no network, no wait.
The "fresh chart" in step two is a render like this one, produced from a data string and a handful of parameters with nobody opening a design file:
signed by Lollyvector SVGCheck it yourselfGet the signed file15 paths~1.3k nodes4 groups21 KB
signed by Lollyvector SVGCheck it yourselfGet the signed file15 paths~1.3k nodes4 groups21 KB
The point isn't that Lolly is good for designers and good for developers and good for sales, each in a vacuum. It's a relay race: the creative's initial work is scaled by the developer, which in turn empowers the producer. The effortless experience for the non-technical rep on the plane is only possible because of the rigour the designer set and the developer deployed.
That's the force multiplier. Lolly isn't a drawer of separate tools for separate roles - it's one deterministic asset lifecycle that every role touches, and each hand it passes through multiplies the value of the last.
One tool, many outputs
The brand rules live in the tool, not in each file it makes (see How Lolly compares). Get the tool right once and every output inherits the same type, colour and spacing, whether that is one card or a spreadsheet of rows in a dozen languages. How you check your work, and who does it, stays yours; Lolly makes the thing worth checking smaller and the output faster to make.
The same tool reaches production through four routes:
- A person, in the app. The
/probatch grid: paste or import the rows, get one finished asset per row, download the zip. No design skill, no wait. - A developer, from the command line. The CLI runs the same engine and the same render path headless, so the tool can be sequenced over every row in a script or a nightly pipeline. A
lolly <tool> --field=…call in a loop is the whole integration. - A system or an AI agent, over MCP. The same tool operated programmatically, at the same fidelity, for as many rows as the job has.
- An organisation, through lolly.work. The server render API applies identity and tool policy, accepts recoverable jobs and batches, and retains outputs for retrieval or governed delivery. The deployment supplies persistent storage and the render workers its tools require.
signed by Lollyvector SVGCheck it yourselfGet the signed file117 paths~9.8k nodes216 groups2 images142 KB
signed by Lollyvector SVGCheck it yourselfGet the signed file117 paths~9.8k nodes216 groups2 images142 KB
One set of tool rules supports local creation, pipeline automation and organisation production. Supported formats and host capabilities determine which route a particular job can use.
The big picture: how the layers fit
Everything from here down is architecture. The diagram is the whole system in one view: tools are data at the top, the engine in the middle knows nothing of any platform, the shells below it implement one contract, and the catalogs supply the content.
Repository layout
Lolly is one repository. engine/, schemas/, scripts/, tests/, api/, docs/, community/, brands/lolly-start/, every shells/ and both services/ are plain directories in it. The one exception is brands/suse, a private git submodule holding the SUSE tool pack and catalog, opt-in and absent from a public clone. Which packs a given build reads is a content profile (profiles.json), resolved per process rather than switched globally. See Build Guide » Getting the source for the clone command and how a change inside the private pack is committed.
lolly/
├── engine/ # Platform-agnostic core. Open source (MPL-2.0).
│ └── src/
│ ├── index.ts # public surface - loader, runtime, template, inputs, url-mode
│ ├── loader.ts # fetches and validates tool files
│ ├── runtime.ts # orchestrates the 5-step lifecycle
│ ├── template.ts # Handlebars hydration + annotateTemplate
│ ├── inputs.ts # manifest → runtime input model
│ ├── url-mode.ts # URL ↔ input state round-trip
│ ├── validate.ts # JSON Schema validation of manifests
│ ├── compose.ts # resolve nested tool renders (composes)
│ ├── embed.ts # parse portable lolly.tools embed URLs
│ └── bridge/
│ └── host-v1.ts # type re-export of the @lolly-tools/core contract
│
├── shells/
│ ├── web/ # PWA - hosted online; primary distribution
│ │ └── src/
│ │ ├── main.ts # boot, routing
│ │ ├── theme.ts # theme apply/persist (FOUC prevention)
│ │ ├── bridge/ # web implementations of HostV1 APIs
│ │ │ ├── index.ts # compose all bridge pieces
│ │ │ ├── db.ts # IndexedDB setup
│ │ │ ├── state.ts # host.state - saved edits
│ │ │ ├── profile.ts # host.profile - user details
│ │ │ ├── assets.ts # host.assets - catalog + user uploads
│ │ │ ├── clipboard.ts # host.clipboard
│ │ │ ├── export.ts # host.export - rasterise/serialize
│ │ │ ├── net.ts # host.net - allowlisted fetch
│ │ │ └── media.ts # host.media - live camera frames (onFrame)
│ │ ├── catalog/
│ │ │ └── sync.ts # boot-time catalog sync + offline cache
│ │ ├── styles/ # app-wide CSS (app.css, picker.css, tokens.css)
│ │ └── views/
│ │ ├── gallery.ts # tool library listing + saved-state cards
│ │ ├── tool.ts # mounts one tool (inputs + canvas + actions)
│ │ ├── picker.ts # asset picker UI (invoked by host.assets)
│ │ ├── profile.ts # user details editor
│ │ ├── projects.ts # /p - folders of saved sessions (nested; folder/selection export)
│ │ └── free-canvas.ts # free-canvas editor overlay for render.layout:"editor" tools
│ │
│ ├── cli/ # Node.js CLI - same engine, headless jsdom
│ │ ├── bin/lolly.ts
│ │ └── src/
│ │ ├── run.ts # loadTool → createRuntime → export → write file
│ │ └── bridge.ts # CLI implementation of HostV1
│ │
│ ├── tui/ # Interactive terminal shell (Ink) - reuses the CLI bridge
│ │ └── src/
│ │ ├── main.tsx # full-screen app: Gallery / Projects / Profile / ToolView
│ │ └── bridge.ts # CLI bridge + on-disk state under ~/.lolly
│ │
│ ├── tauri-desktop/ # downloadable desktop app
│ └── tauri-mobile/ # iOS/Android app
│
├── community/ # the brand-agnostic tool pack - data, not code. Public (MPL-2.0).
│ # A SELECTION follows; a profile mounts these plus whatever
│ # tools the active brand pack carries of its own.
│ ├── qr-code/
│ ├── snippet/
│ ├── countdown-timer/
│ ├── color-palette/
│ ├── street-map/ # offline vector city-block maps
│ ├── url-shot/ # "URL Screenshot" (capture capability)
│ ├── strip-data/ # on-device metadata strip - JPEG/PNG/SVG/PDF (file in → clean file out)
│ ├── compress-pdf/ # on-device PDF compressor - recompresses images (file in → smaller file out)
│ ├── chart/ # SVG charts from structured data
│ ├── filter/ # photo effects in one tool - halftone/scanline/posterize/voronoi (vector), duotone/pixel-stretch/imperfections (raster)
│ ├── meeting-planner/ # global timezone meeting scheduler
│ ├── calendar-ics/ # event → .ics calendar file plus a card
│ ├── wayfinding-signage/ # event signage; directions blocks auto-fit label text
│ ├── text-helper/ # on-device text workbench (format/decode/hash/de-identify)
│ ├── design/ # "Design" - freeform WYSIWYG editor canvas (render.layout: editor)
│ ├── multi-page-pdf/ # multi-page PDF document - cover, flowing content blocks, back page
│ ├── diagram-builder/ # org / layercake / process / cycle / pyramid diagrams
│ ├── logo-wall/ # many logos → auto-packed grid
│ ├── icon/ # favicon .ico / png / svg from text + colours
│ └── lottie-digi-ad/ # animated Lottie ad banners
│
├── brands/ # brand packs - a catalog each, and optionally tools of their own
│ ├── lolly-start/ # the blank starter brand, owned here
│ │ └── catalog/
│ │ ├── tools/index.json # tool registry, generated per brand
│ │ └── assets/
│ │ ├── index.json # asset registry
│ │ └── lolly/... # logo, palette, tokens
│ └── suse/ # PRIVATE submodule - the SUSE tools and the SUSE catalog
│
├── schemas/ # JSON Schema for tool.json, asset entries, AssetRef
├── scripts/ # build-catalog-index.ts, checksum-assets.ts, validate-catalog.ts
├── tests/ # engine tests
└── docs/ # this file + authoring guides + positioningPlatform delivery model
The platform runs across several surfaces - web PWA, Tauri desktop/mobile, the scriptable CLI and the interactive TUI. All of them use the same engine and the same tool files.
Web (PWA) - primary distribution
Hosted at a SUSE-controlled URL. Works offline once the service worker has cached tools and assets. This is where most employees, vendors and partners will use the platform. No account required - state is stored in IndexedDB per device.
The web shell is responsive from one layout. On desktop a tool is a resizable controls sidebar beside a preview stage with trackpad-native canvas navigation (Cmd/Ctrl-wheel or pinch to zoom about the cursor, Space- or middle-drag to pan, 0/1/+/− keys and a Fit/% HUD). On mobile (≤640px) the controls become a top-anchored sheet with a drag grip that snaps peek/half/full (tap toggles) over a static full-screen preview, and a floating Render button opens the Export controls in a bottom-sheet popup. Touch gets pinch-zoom and drag-pan on the preview. The render path and the export controls are identical across both - only the chrome reflows.
signed by Lollyvector SVGCheck it yourselfGet the signed file6 paths~1.3k nodes11 groups17 KB
signed by Lollyvector SVGCheck it yourselfGet the signed file6 paths~1.3k nodes11 groups17 KB
The same tool at phone width, with no second layout to maintain: the controls become a sheet at the top, the preview holds the whole screen and the render pill floats over it.
signed by Lollyvector SVGCheck it yourselfGet the signed file101 paths~3.4k nodes116 groups115 KB
signed by Lollyvector SVGCheck it yourselfGet the signed file101 paths~3.4k nodes116 groups115 KB
Batch mode (/pro). The web shell also ships a spreadsheet-style batch grid (shells/web/src/pro/) that renders many rows at once across one or many tools. It does CSV/TSV round-trip plus spreadsheet paste, per-row template/format/size/unit/dpi, a blocks-editor side panel with a live preview, collapsible export columns, a per-row "relevance" tag bar, left drag-handle row reorder, two-step delete confirm, saved batch sessions and a .zip download. This is the one-to-many surface behind the "mass content generation" positioning.
Tauri desktop / mobile
Packaged native app (small footprint via Tauri). Provides full offline availability, filesystem access for CLI-dependent tools (PDF Smasher, Font Outliner) and camera access. Scheduled for mid-2026 tooling enhancement.
CLI
lolly <tool-id> [--input=value ...] --output=file.png
Desktop users can invoke many tools from the terminal. The CLI shell loads the same engine, creates a jsdom DOM, runs the same render path and writes the file. URL mode is the transport - CLI is not a separate implementation. This guarantees CLI and GUI outputs are identical.
lolly qr-code --url=https://suse.com --output=qr.svg
lolly quotes --quote="Ship it." --output=quote.png
lolly # lists available tools
lolly qr-code # lists inputs for that toolTUI
pnpm run tui
The interactive counterpart to the CLI: a full-screen, keyboard-first terminal app (built on Ink) for browsing tools, filling in inputs, saving projects and exporting - all without a GUI. Its host bridge reuses the CLI's implementation for the DOM-free formats (SVG/EMF/EPS/HTML + text/data), and adds on-disk state under ~/.lolly plus an opt-in inline preview. Beyond that it has a browser render tier: a scoped headless Chromium (the same one the MCP server installs) that produces raster/PDF/video and live-URL capture on demand - driving a built copy of the web shell so output is identical, and launching only when you first export such a format. So url-shot (with crop + recolor + vector PDF/SVG) and every raster/pdf tool run in the terminal too. See the TUI guide.
Whichever surface you are on, the dashboard's Capabilities tab is the full map of what the platform declares it can do, grouped and readable without opening a single tool.
Tool categories
Tools are tagged with a category in their manifest for gallery grouping.
Rows are listed in gallery section order. The utility section always renders last in the gallery (after every other category, including future ones) - it's the on-device "Offline Utilities" drawer.
| Category | Examples | Planned |
|---|---|---|
everyone | QR Code Generator, Quote Card, Email Signature, Logo, Wordmark, Audiogram, Battlecards, Sequence, Record | Employee Image Stationery |
designer | Brand Lockup, Design, Chart, Darkroom, Filter, Pose Geeko, Booklet | Font Outliner |
event | Meeting Planner, Event Name Badge, Wayfinding Signage, Calendar ICS, Booth Studio | Event Stationery, Bulk Name Badges, Room Agenda Cards |
product | - | CVE Alert, Product Release Announcement, Blog OG Image |
utility | Strip Hidden Data, Text, Compress PDF, Convert Image, Convert Font, Redact, Run Web Code, Screen Capture, URL Screenshot | Unit/format converters, more on-device privacy utilities |
Those cells are examples, not inventories. Which tools exist is a property of the profile you mounted, not of this page: a brand pack adds its own, and can exclude a community tool it would rather not ship. catalog/tools/index.json - generated from the manifests, and the registry the gallery actually reads - is the authoritative list; to count what a profile mounts, count the manifests (ls community//tool.json brands//tools/*/tool.json) rather than trusting a number written down here. (A tool id present in two packs mounts once, from the winning pack.)
Tools are also classified by status: official (brand approved, no watermark), community (external contribution), experimental (watermarked exports). Most of the library is official; the newer studios and the capture tools tend to sit at community or experimental while they settle. Every surface shows the badge, so a reader knows what they are picking up before they open it - and, like the category cells above, the per-status membership moves too fast to enumerate here. Read it off the gallery or the generated index.
Design is the first tool built on the render.layout: "editor" free-canvas mode - a chromeless, direct-manipulation surface where you drag, resize, rotate and snap boxes of text, shapes and images, then export through the same render path as every other tool.
Strip Hidden Data is the first on-device utility (privacy: "on-device"): a content-transform tool that takes a file you supply, processes it entirely in the browser and hands back a clean copy - never uploaded, never watermarked, no provenance stamped. Text is the second - an on-device workbench for everyday paste-into-a-website jobs (JSON format, JWT decode, Base64, URL encode/decode, SHA hashing). Compress PDF is the third - it shrinks a PDF by recompressing its images, again entirely on-device. The marker and its badge text "Runs on your device - nothing is uploaded" now cover the whole transform set: Strip Hidden Data, Text, Compress PDF, Convert Image (HEIC/TIFF/AVIF → WebP/JPG/PNG), Convert Font, Redact (destroy regions of an image, SVG or PDF), Prompt Card and Rebrand (re-theme a .pptx in place) where the profile mounts it. This is a privacy-utility category that replaces handing confidential files to single-purpose websites.
signed by Lollyvector SVGCheck it yourselfGet the signed file120 paths~26k nodes308 groups551 KB
signed by Lollyvector SVGCheck it yourselfGet the signed file120 paths~26k nodes308 groups552 KB
Note:
categoryandstatusare denormalised intocatalog/tools/index.json(the registry the gallery reads) from eachtool.json. The manifest is the source of truth - the index is generated bypnpm run build:catalogandpnpm run validate:catalogfails CI if the committed index drifts from the manifests.
Architectural commitments
These decisions are settled. Changing any of them is a major undertaking - they shape every other decision in the codebase.
1. Declarative tools, with an imperative escape hatch
A tool is a manifest (tool.json) + a template (template.html) + optional hooks.js.
The manifest declares inputs. Not the template. Inputs are not inferred from Handlebars tokens. The manifest is the contract; the template consumes named variables by {{id}}.
signed by Lollyvector SVGCheck it yourselfGet the signed file34 paths~2.5k nodes54 groups2 images40 KB
signed by Lollyvector SVGCheck it yourselfGet the signed file34 paths~2.5k nodes54 groups2 images40 KB
Hooks are optional. Most tools are pure declarative - manifest + template is enough. Tools needing computed values (QR encoding, chart data shaping) provide hooks.js exposing named lifecycle functions (onInit, onInput, onFrame - the per-frame live-camera hook for motion-reactive tools - onLevel, beforeExport, afterExport, exportFile - the file-in/file-out transform path used by on-device utilities like Strip Hidden Data - and exportStill, for a tool that owns its own deep raster). The host loads hooks via new Function('host', …) with the capability bridge injected as closure scope. This is a portability contract, not a security sandbox: hooks still run in the page realm and can reach window/fetch/document in a browser shell - host. is the supported, portable surface, not an enforced boundary. Async hook results are time-boxed (onInit 5s, onInput 2s, beforeExport/afterExport 5s, exportFile/exportStill 10s) and late results discarded; a runaway synchronous* hook cannot be preempted. Untrusted third-party hook code is therefore not safe to run until Worker isolation ships.
This matters because: declarative tools can be authored by non-developers. If every tool were a web app, the risk note "limited skills to create/maintain workhorse templates" becomes a permanent bottleneck.
2. Tools and assets are data, not bundled code
The web and Tauri apps fetch tool and asset catalogs from a known URL at boot, cache them locally and operate on whatever is there. Adding a new event tile or seasonal asset does not require an app release.
Asset bytes are SHA-256 checksummed to prevent CDN poisoning. Asset id + version drives cache invalidation.
3. The Capability Bridge is the only API tools see
Tools never touch the DOM outside their template area, never call fetch directly, never read the filesystem. They call versioned host.* methods. The contract's canonical definition is packages/core/src/host-v1.ts - the tool-author SDK @lolly-tools/core, so a third party can build against it without depending on the engine; engine/src/bridge/host-v1.ts is a type re-export of it, and engine/shell code keeps importing from that path unchanged:
| Bridge API | What it does |
|---|---|
host.profile | User's firstname, email, headshot, city, etc. Pre-fills inputs via bindToProfile. |
host.assets | Catalog queries, asset resolution, host-provided picker UI. |
host.state | Save / load input slots. IndexedDB on web, filesystem on Tauri, memory on CLI. |
host.clipboard | Write text or image to clipboard (with platform fallbacks). |
host.export | Rasterise or serialise the render target. Applies watermark for experimental tools. |
host.net | Allowlisted fetch - only available if the tool declared "network" capability. (No shipping tool currently uses it.) |
Optional, additive surfaces appear only when a shell provides them. Some are capability-gated - exposed only when the tool declares the matching flag: host.compose (embed another tool's render - compose), host.capture (page capture for URL Screenshot - capture) and host.recorder (mic/camera/display capture for the recording tools - microphone / camera / screen). The rest are feature-detected - present whenever the shell can provide them, with the tool keeping a fallback for shells that can't.
A handful of headline surfaces, to show what it covers - Host API documents every one, and packages/core/src/host-v1.ts is the contract itself:
| Surface | Since | What it adds |
|---|---|---|
host.tokens | 1.0 | DTCG design tokens - the brand's own primitives |
host.text | 1.0 | Text-to-path via HarfBuzz WASM (the wasm capability flags tools that rely on it) |
host.media | 1.4 | Live camera frames driving the onFrame hook. Progressive enhancement, deliberately not gated by the camera flag - such a tool still works as an ordinary still-image tool |
host.color | 1.40 | Perceptual colour maths: ΔEOK, WCAG + APCA contrast, OKLab ramps, class-breaks, categorical palettes, harmony schemes (1.60), CSS Color 4 mixing and gradient baking (1.68). Pure and synchronous - shells attach the engine's makeColorApi() rather than implementing anything, so it cannot drift |
host.images | 1.60 | Decode / resize / re-encode bytes on device - the convert path (HEIC → JPEG, compress to WebP, downscale). Shipped in the web shell as a lazy facade, so the HEIC decoder never enters the boot chunk |
host.geom | 1.64 | Exact vector geometry: path booleans, offsetting, stroke-to-fill, spline lowering, simplification, hit testing. Also pure, synchronous and attached from the engine (makeGeomApi()); failures are returned, never thrown |
The rest follow the same rules and are documented alongside them: pdf (1.8) and pptx (1.58) for on-device document surgery, audio (1.71) and speech (1.96) for clip analysis and on-device TTS/transcription, viz (1.72) for the MilkDrop placeholder contract, codec (1.100) and layers (1.102) for deep-bit and layered-bitmap output, upscale (1.101) and matte (1.103) for the on-device models, raster (1.105) for hooks doing their own pixel work, connectors (1.106) for export-safe arrows and c2pa (1.85) for signing finished bytes. The count grows; the rules don't.
The declarable capabilities are: network, filesystem, clipboard, camera, microphone, screen, ffmpeg, wasm, capture, compose. (screen, added in 1.54, is display capture via host.recorder - the user picks a screen/window/tab in browser-native UI; distinct from capture, which rasterises a URL the tool itself names.)
The same tool runs in browser, Tauri and headless CLI because each shell implements this interface - the tool never knows which it's in.
The bridge is versioned. Adding methods is a minor version. Removing or changing signatures is a major version bump. When v2 ships, v1 must continue to work.
4. Asset IDs are forever
suse/logo/primary is a contract. Once published:
- The ID never changes, never gets reused.
- Byte changes → bump
versionin the manifest. - Replaced by a new asset → set
deprecated: trueand optionallyreplacedBy. - Existing references always resolve.
This makes saved tool states and URL-shared links durable across years.
5. URL mode is first-class
Every input must be expressible as a URL parameter:
lolly.tools/#/tool/qr-code?url=https://suse.com&ecl=Hsigned by Lollyvector SVGCheck it yourselfGet the signed file59 paths~2.6k nodes50 groups63 KB
signed by Lollyvector SVGCheck it yourselfGet the signed file59 paths~2.6k nodes50 groups63 KB
CLI mode is URL mode under a different transport - the CLI shell builds a URL-state object from argv and runs the same engine pipeline. There is one render path. CLI cannot drift from GUI because it isn't a separate implementation.
url-mode.ts handles the round-trip (parse and serialize). A set of reserved params is never forwarded to the tool as inputs: the output controls (format, export, copy, filename, width/w, height/h, unit, dpi), the print and provenance dials (bleed, marks, profile, password, c2pa, imprint, durable, meta, hdr, depth, cuts) and the state carriers (template, z - the "Shortest link" packed token - and zx, the same encrypted under a password). The RESERVED set in engine/src/url-mode.ts is the authority and is pinned by a test; URL Mode documents every one of them, including the handful not listed here. Asset inputs in URL mode are serialised by their id; the runtime resolves them via host.assets.get() before hydration. width/height are values in unit (default px, also mm/cm/in/pt/pc); with a physical unit dpi sets raster resolution. They set the canvas document size and pre-fill the export dimensions panel.
Because every input travels in the link, a parameter change is a different finished asset. This whole palette is one seed colour, a harmony and a step count:
signed by Lollyvector SVGCheck it yourselfGet the signed file10 groups25 KB
signed by Lollyvector SVGCheck it yourselfGet the signed file10 groups25 KB
6. Storage goes through the bridge, not direct
Web shell: IndexedDB. Tauri: filesystem. CLI: in-memory. Tools see only host.state.save(slot, data) and host.state.load(slot). localStorage is not used - it's too small and can't hold blobs.
Users can save multiple named edit slots per tool and return to each session later. No account creation is required; state is per-device. Because the bridge is the only seam, that per-device state is also portable: shells/web/src/data-transfer.ts reads everything back out through host.profile/host.state/host.assets into a single lolly-backup zip that imports on any other install - the offline answer to "move to a new device" that doesn't need a server (full spec: docs/data-transfer.md). Multi-device sync already runs on top of this, sending the same bundle to storage the person chooses (docs/sync.md).
7. Maturity tags say what a tool is
Every tool declares status: official | community | experimental in its manifest. The gallery sorts by status. Experimental tools watermark their exports automatically - the watermark is applied by host.export.render, not by the tool, so it cannot be opted out of by a non-official tool author.
This is a structural answer to the perception risk that every tool in the gallery carries the same weight. Whatever process a team puts around the catalog sits on top of it.
8. Tool inputs are typed via the manifest, including assets
Inputs declare a type: text, longtext, number, boolean, color, select, asset, date, time, datetime-local, url, blocks, vector, table and file. The host renders a generic control per type from the manifest - tools write zero control code. (Pre-filling from the user's profile is not a type - any input can carry bindToProfile.) Three carry more weight than the rest:
asset(withfilterandallowUpload) is the bridge to the global asset system;allowUpload: falseis the brand-enforceability lever for things like sponsorship-tile logos where only library assets are permitted. User uploads use the sameAssetRefshape as library assets, so tools handle them identically.blocksis a repeating field-group - a mini-table inside one input, edited in a side panel, with a typed/discriminated add menu and per-block asset fields. Clicking a rendered block on the canvas focuses that block's row. Used bymeeting-planner,chart-creator,event-name-badge,wayfinding-signage,color-blockanddigi-ad.vectorgroups a fixed set of numbers (e.g. a transform) into one compound control;fileholds the user's own file as bytes in memory for on-device transform utilities (e.g.strip-dataandcompress-pdf).
9. Templates are logic-less (Handlebars, not EJS)
Handlebars was chosen over EJS deliberately:
- Logic-less. Templates can be authored by non-developers.
- Safe by default.
{{x}}HTML-escapes;{{{x}}}is opt-in raw. - No arbitrary JS in templates means no per-template XSS audit surface.
Logic lives in hooks.js where it is explicit and reviewable. Available Handlebars helpers: {{default}}, {{upper}}, {{lower}}, {{eq}}, {{markdown}}, {{asset ref}}, {{asset ref "property"}} (plus data-format helpers icsStamp/rfcText/csvCell used by sibling .ics/.vcf/.csv templates).
10. Tools compose tools
A tool can embed another tool's render with no tool-to-tool imports - composition is resolved by the engine, never by tool code. There are two surfaces:
- Declarative manifest -
composes: [{ id, tool, inputs, format?, width?, height? }]. The engine renders the named child and places the result in the logic-less template as{{asset <id>}}.event-name-badgecomposesqr-codeas an SVG today. - Portable embed URL -
<img src="https://lolly.tools/tool/<id>.<ext>?<inputs>">. The shell renders that child locally (a placeholder pixel shows until the local render resolves); nothing is ever fetched fromlolly.tools.
Compose any tool's render: an SVG child stays a true vector when the parent exports to SVG or PDF and rasterises crisply for PNG; PNG/JPG/WEBP children embed as images. Requires the compose capability. Composed children are intermediates - never watermarked or provenance-stamped - and composition degrades gracefully: a shell that can't render a child just omits the slot and the parent still renders.
What we explicitly chose not to do
- No EJS / no arbitrary JS in templates. XSS surface is zero. Logic lives in
hooks.js. - No mandatory asset CMS. Individuals ingest their own creative files straight into Assets in-app (Assets and the Brand Studio) - no server, no admin console. Work is handed on as a session: a share link carries its inputs and settings (uploaded files travel in a
.lolly), and the same session travels in a backup or over a collab session. Whoever controls the deployment can then lock a shared session in as a template - open the link, record its values as a template entry in that tool's directory in the brand pack and commit - after which it appears in the tool's "New from template" chooser and is deep-linkable as?template=<id>. Git is the deployment owner's locking step, never the creator's. For a shared, governed catalog, an organisation can manage the asset directory the same way and gate updates through PR review - an available governance model, not a requirement of the app. - No forced RBAC. The open app is public-access by default; brand risk is managed by maturity tags + watermarks. An org that wants tighter control layers on its own auth and the git-reviewed catalog above.
- No central database. All user state is per-device; the optional sync copies it to storage the person chooses, never to a Lolly database.
- No shared tools/engine code path. The engine is open source and so are the brand-agnostic tools in
community/; a brand pack such as the privatebrands/suse/carries its own tools and catalog under its own terms. Either way the separation is enforced (no cross-imports fromengine/into tool content) so the split stays clean.
The lifecycle of one render
A user opens lolly.tools/#/tool/qr-code?url=https://suse.com&ecl=H:
- Boot. Web shell opens IndexedDB, constructs the capability bridge, syncs the tool and asset catalogs (or loads from cache when offline).
- Route. URL hash →
toolview, withqr-codeand URL params extracted. - Load.
loadTool('qr-code', fetchFile)fetchestool.json, validates against the JSON Schema, fetchestemplate.html,styles.cssandhooks.jssource. - Parse URL state.
parseUrlStatetranslates URL params into initial input values. Asset refs (?logo=suse/logo/primary) are parsed as lightweight{ id, _unresolved: true }objects. - Runtime.
createRuntime(tool, host, initialValues)builds the input model (merging profile data, defaults and initial values), resolves asset refs viahost.assets.get(), loads hooks (closure-scopedhost, not sandboxed), callshooks.onInit. - Render. Shell subscribes to runtime; on every state change it receives
{ model, hydrated }. It renders input controls from the model and writes the hydrated template HTML into#tool-canvas. - Interact. User types in an input →
runtime.setInput(id, value)→ constraints applied →hooks.onInputcalled → re-hydrate → re-render. The canvas updates live. - Export. User clicks Download(PNG) →
runtime.export(canvasNode, 'png')→host.export.render(rasterises via dom-to-image-more; SVG/PDF go through dedicated DOM-walking vectorisers) → blob →host.export.download. The format range a tool can opt into is broad, and therender.formatsenum inschemas/tool.schema.jsonis the authority on it - rasters and float rasters, vectors and cut files, print/CMYK, motion, editable documents (pptx,docx,odt), palette and data/text outputs, audio and font files. URL Mode lists every id and what it produces. Audio is in that enum like anything else (wav,mp3,m4a,opus, declared by the audiogram and the recording tools); separately, a recording tool'srender.capturemode driveshost.recorder, whose take arrives as a finished Blob in whatever container the browser recorded. (Tools that setrender.export: false- e.g. Color Palette, Countdown Timer, Strip Hidden Data, Text, Compress PDF - hide the download/format/dimension controls.) Physical units are converted per format here (PDF → true page points, raster → pixels at DPI with apHYschunk). Authorship/provenance metadata (author, tool, source - built byengine/src/metadata.ts) is embedded per format: PNG iTXt, JPEG EXIF, PDF info dict, SVG<metadata>, GIF comment. Experimental tools get a watermark inserted by the host, not the tool.
signed by Lollyvector SVGCheck it yourselfGet the signed file60 paths~4.0k nodes76 groups2 images62 KB
signed by Lollyvector SVGCheck it yourselfGet the signed file60 paths~4.0k nodes76 groups2 images62 KB
Same lifecycle in Tauri. Same lifecycle in CLI - jsdom provides the headless DOM; output goes to a file or stdout.
Open-source status
Code is MPL-2.0. engine/, shells/, services/, schemas/ and the documentation generator are open source under MPL-2.0 - a vendor-neutral scaffolding platform for brand tooling, all of it in one public repository, lolly-tools/lolly.
Documentation content is CC BY-SA 4.0, with attribution to lolly.tools. The documentation licence covers the prose and original illustrations; code examples use MPL-2.0, and third-party material keeps its stated licence.
Tool content ships as brand packs, each with its own terms (see the pack's NOTICE.md). community/ is a directory of this repository and its brand-agnostic tools are MPL-2.0 too. brands/suse/ is the private suse-lolly pack, the one submodule: the SUSE tools and the SUSE catalog, proprietary to SUSE, including its licensed PremiumBeat music. brands/lolly-start/ is the blank starter brand this repository owns. Fonts ship inside a pack under the SIL Open Font License 1.1 - the SUSE pack carries the SUSE and SUSE Mono typefaces.
There is no repo-root tools/ or catalog/ directory. packages/node-shell/src/content-roots.ts answers "where does tool <id> live" and "where is the catalog" from profiles.json at read time, so a profile is a per-process answer and no tree has to be assembled first. A real tools/ + catalog/ pair is written only into a build output - dist/, an RPM payload, a container image - because a browser fetches those two paths over HTTP.
The split is enforced - there are no cross-imports from engine/ into tool content - so the platform/content boundary stays clean.
Where the engine ends and the host begins
If you can describe it in pure data + Handlebars → engine. If it touches the DOM, filesystem, network or any browser/OS API → host.
The line is sharp on purpose. The engine is the open-source part. Everything that knows about SUSE, specific platforms or runtime environments stays out of it.
For the next level of detail, engine/README.md enumerates every engine module and what it is responsible for, and Threat Model & Trust Boundaries records where that same line doubles as a trust boundary.