Lolly Beta
Launch App

Overview

signed by Lollyoverview.htmlHTMLAI generatedgenerated by ClaudeCheck it yourselfGet the signed filepixels, not shapes12 KB

Lolly Icon - Large green and white lollipop candy

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.md in 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 KB

The 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.

Every tool in the library as a card, grouped by category, so a producer picks one and startssigned by Lollyvector SVGCheck it yourselfGet the signed file384 paths~34k nodes795 groups4 images1,652 KBEvery tool in the library as a card, grouped by category, so a producer picks one and startssigned 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:

  1. 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.
  2. 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.
  3. 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:

A titled stacked area chart, its three series banded in a cool palette with axes, legend and title all placed by the template rather than by handsigned by Lollyvector SVGCheck it yourselfGet the signed file15 paths~1.3k nodes4 groups21 KBA titled stacked area chart, its three series banded in a cool palette with axes, legend and title all placed by the template rather than by handsigned 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:

Batch mode on a fresh install: one empty row waiting for a tool, with the whole spreadsheet surface and its Render button in place before any data arrivessigned by Lollyvector SVGCheck it yourselfGet the signed file117 paths~9.8k nodes216 groups2 images142 KBBatch mode on a fresh install: one empty row waiting for a tool, with the whole spreadsheet surface and its Render button in place before any data arrivessigned 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.

Tools supply a manifest, template and hooks to the shared engine. Web, Tauri and terminal shells implement the HostV1 capability bridge and read tools and assets from catalogs.Tools supply a manifest, template and hooks to the shared engine. Web, Tauri and terminal shells implement the HostV1 capability bridge and read tools and assets from catalogs.Open this in Diagram Builder

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 + positioning

Platform 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.

The desktop split view - controls generated from the manifest on the left, the live canvas on the rightsigned by Lollyvector SVGCheck it yourselfGet the signed file6 paths~1.3k nodes11 groups17 KBThe desktop split view - controls generated from the manifest on the left, the live canvas on the rightsigned 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.

An audiogram on a 430px-wide screen - the controls sheet above, the finished square artwork below and the floating render pillsigned by Lollyvector SVGCheck it yourselfGet the signed file101 paths~3.4k nodes116 groups115 KBAn audiogram on a 430px-wide screen - the controls sheet above, the finished square artwork below and the floating render pillsigned 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 tool

TUI

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.

CategoryExamplesPlanned
everyoneQR Code Generator, Quote Card, Email Signature, Logo, Wordmark, Audiogram, Battlecards, Sequence, RecordEmployee Image Stationery
designerBrand Lockup, Design, Chart, Darkroom, Filter, Pose Geeko, BookletFont Outliner
eventMeeting Planner, Event Name Badge, Wayfinding Signage, Calendar ICS, Booth StudioEvent Stationery, Bulk Name Badges, Room Agenda Cards
product-CVE Alert, Product Release Announcement, Blog OG Image
utilityStrip Hidden Data, Text, Compress PDF, Convert Image, Convert Font, Redact, Run Web Code, Screen Capture, URL ScreenshotUnit/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.

The Utilities drawer, where every card is a tool that transforms a file you already havesigned by Lollyvector SVGCheck it yourselfGet the signed file120 paths~26k nodes308 groups551 KBThe Utilities drawer, where every card is a tool that transforms a file you already havesigned by Lollyvector SVGCheck it yourselfGet the signed file120 paths~26k nodes308 groups552 KB

Note: category and status are denormalised into catalog/tools/index.json (the registry the gallery reads) from each tool.json. The manifest is the source of truth - the index is generated by pnpm run build:catalog and pnpm run validate:catalog fails 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}}.

Street Map's control stack - a city dropdown, a theme select, weight sliders and colour triggers, every one of them drawn from a manifest linesigned by Lollyvector SVGCheck it yourselfGet the signed file34 paths~2.5k nodes54 groups2 images40 KBStreet Map's control stack - a city dropdown, a theme select, weight sliders and colour triggers, every one of them drawn from a manifest linesigned 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 APIWhat it does
host.profileUser's firstname, email, headshot, city, etc. Pre-fills inputs via bindToProfile.
host.assetsCatalog queries, asset resolution, host-provided picker UI.
host.stateSave / load input slots. IndexedDB on web, filesystem on Tauri, memory on CLI.
host.clipboardWrite text or image to clipboard (with platform fallbacks).
host.exportRasterise or serialise the render target. Applies watermark for experimental tools.
host.netAllowlisted 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:

SurfaceSinceWhat it adds
host.tokens1.0DTCG design tokens - the brand's own primitives
host.text1.0Text-to-path via HarfBuzz WASM (the wasm capability flags tools that rely on it)
host.media1.4Live 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.color1.40Perceptual 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.images1.60Decode / 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.geom1.64Exact 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:

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=H

That link on its own, with nothing else in it, is the finished assetsigned by Lollyvector SVGCheck it yourselfGet the signed file59 paths~2.6k nodes50 groups63 KBThat link on its own, with nothing else in it, is the finished assetsigned 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:

Nine steps across four hues, all grown from the single seed colour carried in the linksigned by Lollyvector SVGCheck it yourselfGet the signed file10 groups25 KBNine steps across four hues, all grown from the single seed colour carried in the linksigned 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:

9. Templates are logic-less (Handlebars, not EJS)

Handlebars was chosen over EJS deliberately:

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:

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


The lifecycle of one render

A user opens lolly.tools/#/tool/qr-code?url=https://suse.com&ecl=H:

  1. Boot. Web shell opens IndexedDB, constructs the capability bridge, syncs the tool and asset catalogs (or loads from cache when offline).
  2. Route. URL hash → tool view, with qr-code and URL params extracted.
  3. Load. loadTool('qr-code', fetchFile) fetches tool.json, validates against the JSON Schema, fetches template.html, styles.css and hooks.js source.
  4. Parse URL state. parseUrlState translates URL params into initial input values. Asset refs (?logo=suse/logo/primary) are parsed as lightweight { id, _unresolved: true } objects.
  5. Runtime. createRuntime(tool, host, initialValues) builds the input model (merging profile data, defaults and initial values), resolves asset refs via host.assets.get(), loads hooks (closure-scoped host, not sandboxed), calls hooks.onInit.
  6. 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.
  7. Interact. User types in an input → runtime.setInput(id, value) → constraints applied → hooks.onInput called → re-hydrate → re-render. The canvas updates live.
  8. 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 the render.formats enum in schemas/tool.schema.json is 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's render.capture mode drives host.recorder, whose take arrives as a finished Blob in whatever container the browser recorded. (Tools that set render.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 a pHYs chunk). Authorship/provenance metadata (author, tool, source - built by engine/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.

The export panel that ?options opens: the filename and format pair, the output size and the controls that write the filesigned by Lollyvector SVGCheck it yourselfGet the signed file60 paths~4.0k nodes76 groups2 images62 KBThe export panel that ?options opens: the filename and format pair, the output size and the controls that write the filesigned 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.