Overview

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.


Why this exists

Teams face a recurring problem: repeatable creative and content work that is too predictable to justify skilled hands every time, but too quality-sensitive to hand off without guardrails. The result is either slow throughput (specialist bottleneck), inconsistency (people using whatever tool they have), or vendor lock-in (a SaaS DAM that controls your templates).

This platform is the structural answer:

Programmatic creative and content at scale - zero-labor asset generation, with the rules under central control, for employees, vendors, and partners.

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 without a design ticket. The platform handles recurring operationalised creative. It is deliberately not a bespoke creative tool - designers still own flagship work.

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 reaches ten thousand 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.

Where it fits in the landscape

CapabilityCanvaBrand portalsIllustratorFigma / PenpotLolly
Mass content generationpartial
Works fully offlinepartial
Template logic & hard constraintspartialpartial
No design skill requiredpartial
Automatic Content Credentialspartial
Tools compose other tools
Open engine, not SaaS-lockedpartial
C2PA content credentials
Opt-in forensic-level provenance
Mobile and Desktop Appspartial
Command Line & TUI

The gap is clear: nothing in the existing landscape gives us constraints-first, offline-capable, low-skill, internally accessible output. Lolly even includes an open canvas - Layout Studio - where colours, type and assets conform to the brand globals, so free arrangement stays constraints-first. What it is not is an unconstrained design suite: designers continue to use Illustrator and Figma for bespoke flagship work. Permutations can be assembled with this tool.

Every tool in the library as a card, grouped by category, so a producer picks one and starts

Use it for: Rapid generation of operationalised creative assets - event tiles, name badges, signatures, CVE alerts, QR codes, social cards, consignment labels, structured reports.

Do not use it for: Bespoke hero content.


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 Layout Studio, 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 hand

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 approval, ten thousand assets

Because approval lives in the tool and not the file (see How Lolly compares), scale stops being a review problem. Approve a localized social-card tool once, then generate 10,000 assets across 12 languages from a spreadsheet - and not one of them needs a fresh compliance check from legal or brand, because the template they all come from was already approved.

The same deterministic tool reaches that scale three ways, all producing identical, pre-approved output:

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 arrives

One set of brand constraints, fixed once by a designer; three routes to the identical pre-approved output - and the machine route scales furthest of all, because it never tires while the files roll in.


The big picture

                ┌─────────────────────────────────────────────┐
                │              Tools (data, not code)         │
                │   tool.json + template.html + hooks.js?     │
                └─────────────────────────────────────────────┘
                                    ▲
                                    │ talks to via Capability Bridge v1
                                    ▼
                ┌─────────────────────────────────────────────┐
                │                  Engine                     │
                │   loader · validator · runtime · template   │
                │   inputs · url-mode                         │
                │   PLATFORM AGNOSTIC. Knows nothing of DOM,  │
                │   filesystem, or You.                       │
                └─────────────────────────────────────────────┘
                                    ▲
                                    │ implements HostV1
                                    ▼
        ┌──────────────┬──────────────┬──────────────┬──────────────┐
        │  Web Shell   │ Tauri Desktop│ Tauri Mobile │  CLI Shell   │
        │   (PWA)      │              │              │              │
        └──────────────┴──────────────┴──────────────┴──────────────┘
                                    ▲
                                    │ fetches from
                                    ▼
                ┌─────────────────────────────────────────────┐
                │              Catalogs                       │
                │   catalog/tools/index.json + tool dirs      │
                │   catalog/assets/index.json + asset files   │
                └─────────────────────────────────────────────┘

Repository layout

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    # TypeScript interface - the bridge 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
│
├── tools/            # profile VIEW (gitignored) - data, not code. Merged from packs:
│                     #   community/ (public, brand-agnostic, MPL) + brands/<active>/tools (brand-owned).
│   ├── qr-code/
│   ├── quotes/
│   ├── email-signature/
│   ├── daily-card/        # "Day Brief" - weather/time/map (fetched by an inline template script)
│   ├── code-canvas/
│   ├── countdown-timer/
│   ├── color-palette/
│   ├── color-block/           # typed/heterogeneous blocks (addMenu discriminator)
│   ├── dynamic-layout/
│   ├── tool-logo/         # "Logo" - auto-switching brand logo
│   ├── 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)
│   ├── brand-lockup/      # "Brand Lockup" - SUSE logo lockups; HarfBuzz text-to-path (wasm)
│   ├── bag-video/
│   ├── chart-creator/     # SVG charts from structured data
│   ├── filter-duotone/    # two-color photo treatment
│   ├── filter-halftone/   # photo → vector halftone dot grid
│   ├── filter-scanline/   # photo → retro posterised scanline grid (SVG / transparent raster)
│   ├── meeting-planner/   # global timezone meeting scheduler
│   ├── calendar-ics/      # event → .ics calendar file plus a card
│   ├── digi-ad/           # "Animated Ad" - looping banner from scenes
│   ├── event-name-badge/  # conference badges - composes qr-code as an SVG
│   ├── wayfinding-signage/ # event signage; directions blocks auto-fit label text
│   ├── text-helper/       # on-device text workbench (format/decode/hash/de-identify)
│   ├── layout-studio/     # "Layout Studio" - 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
│   ├── logo-lockup-partner/ # SUSE + partner co-brand lockup
│   ├── web-icon/          # favicon .ico / png / svg from text + colours
│   ├── filter-posterize/  # photo → flat posterised vector separations
│   ├── filter-pixel-stretch/ # photo → pixel-smear effect
│   ├── lottie-digi-ad/    # animated Lottie ad banners
│   └── pose-geeko/        # pose the SUSE Geeko mascot - print-ready stills
│
├── catalog/
│   ├── tools/index.json        # tool registry
│   └── assets/
│       ├── index.json          # asset registry
│       └── suse/...            # logo, palette, etc.
│
├── 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 right

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 pill

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

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

CategoryShipped toolsPlanned
everyoneQR Code Generator, Quote Card, Email Signature, Day Brief, Code Canvas, Color Block, Dynamic Layout, Logo, Web Icon MakerEmployee Image Stationery
designerBrand Lockup, Bag Video, Chart Creator, Street Map, Animated Ad, Multi-Page PDF, Diagram Builder, Logo Lockup: Grid (NASCAR), Logo Lockup: Partner, Filter: Duotone, Filter: Halftone, Filter: Scanline, Filter: Posterize Bitmap, Filter: Pixel StretchFont Outliner
eventMeeting Planner, Event Name Badge, Wayfinding Signage, Calendar ICSEvent Stationery, Bulk Name Badges, Room Agenda Cards
product-CVE Alert, Product Release Announcement, Blog OG Image
utilityCountdown Timer, Color Palette, URL Screenshot, Strip Hidden Data, Text Helper, Compress PDF, Layout StudioUnit/format converters, more on-device privacy utilities

Tools are also classified by status: official (brand approved, no watermark), community (external contribution), experimental (watermarked exports). Dynamic Layout, URL Screenshot, Logo Lockup: Grid (NASCAR), Filter: Posterize Bitmap and Diagram Builder currently carry experimental status; Web Icon Maker and Layout Studio ship as community tools.

Layout Studio 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 Helper 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. All three carry the badge text "Runs on your device - nothing is uploaded". This is the start of 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 have

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 npm run build:catalog and npm 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 line

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 - beforeExport, afterExport, and exportFile - the file-in/file-out transform path used by on-device utilities like Strip Hidden Data). (beforeRender is reserved in the hook contract but currently has no invocation site - don't rely on it.) 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, others 5s) 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 bridge is defined in engine/src/bridge/host-v1.ts:

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. Two are capability-gated - exposed only when the tool declares the matching flag: host.compose (embed another tool's render - compose) and host.capture (page capture for URL Screenshot - capture). The rest are feature-detected - present whenever the shell can provide them: host.text (text-to-path via HarfBuzz WASM; the wasm capability flags tools that rely on it), host.pdf (PDF parsing/compression, used by Strip Hidden Data and Compress PDF), and host.tokens (DTCG design tokens). The declarable capabilities are: network, filesystem, clipboard, camera, ffmpeg, wasm, capture, compose.

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 asset

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). Reserved params (never forwarded to the tool as inputs): format, export, copy, slot, output, filename, _v, z (packed state - the "Shortest link" token), width/w, height/h, unit, dpi, profile, password, bleed, marks, full, options, nostage. 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 link

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). SUSE ID integration (multi-device sync) is a future milestone on top of this.

7. Maturity tags answer the "brand approved" risk structurally

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 using any tool implies brand approval. Process answers (a review queue, SUSE ID gating) layer on top.

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, profile, blocks, vector, and file. The host renders a generic control per type from the manifest - tools write zero control code. 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:

The Slides tool is built on that second surface: any slot on any slide can hold another Lolly tool instead of an image.

The opening slide of the default deck, whose own subtitle states that every slide can hold another Lolly tool

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


Lifecycle, end to end

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: svg, png, jpg/jpeg, webp, avif, pdf, the vector formats emf, eps, plus the print/CMYK formats pdf-cmyk, cmyk-tiff, eps-cmyk; the video formats webm, mp4, gif; and data/text formats html, md, txt, json, csv, ics, vcf, ico, zip. Audio is a separate path - the recording tools' render.capture mode drives host.recorder, whose take is saved as MP3 (on-device transcode) or the browser's native m4a/ogg/webm container, so audio never flows through the render.formats enum. (Tools that set render.export: false - e.g. Color Palette, Countdown Timer, Strip Hidden Data, Text Helper, 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 <code>?options</code> opens: the filename and format pair, the output size, and the controls that write the file

Same lifecycle in Tauri. Same lifecycle in CLI - jsdom provides the headless DOM; output goes to a file or stdout.


Open-source status

The engine/, shells/, schemas/, and docs/ directories are open source under MPL-2.0 - a vendor-neutral scaffolding platform for brand tooling, with each shippable unit split into its own repository under github.com/lolly-tools. tools/ and catalog/assets/ are SUSE-specific content and remain proprietary to SUSE (all rights reserved - see each repo's NOTICE.md); they are not covered by the MPL.

The split is enforced - there are no cross-imports from engine/ to tools/ or assets/ - so the platform/content boundary stays clean.


Roadmap

MilestoneTargetWhat
Initial tools✅ DoneQR Code, Quote Card, Email Signature, Day Brief, Code Canvas, Countdown Timer, Color Palette, Brand Lockup, Bag Video, Chart Creator, Filter: Duotone, Meeting Planner - web shell live
Enhance current toolingMid 2026 ✅ DoneDownloadable offline app (Tauri); additional employee and event tools; richer export pipeline (text-to-path stability, metadata, extra formats - see plans.md)
Open source the engineLate 2026 ✅ DoneEngine, shells, schemas, docs go public - not the branded tools/assets
Device-to-device transfer✅ DonePortable lolly-backup bundle carries profile, saved sessions, uploaded images and prefs between any two installs - offline or online, no account. Forward-compatible, integrity-checked envelope (spec: docs/data-transfer.md)
Establish formal tool roadmapLate 2026Customer reference kits, AI design ingest, GET/URL request mode
On-device privacy utilities🚧 In progressContent-transform tools that process your own file locally (file in → clean file out), replacing exfiltration to single-purpose SaaS. Done: file input type + exportFile transform path + privacy:"on-device" conventions (no watermark/provenance) + Strip Hidden Data (JPEG/PNG/SVG/PDF metadata, PDF via the host.pdf bridge) and Text Helper (the on-device workbench for the everyday paste-into-a-website jobs - JSON format, JWT decode, Base64, URL encode/decode, SHA hashing, plus a Novelty group). Next: crop/resize, image convert/compress; then a host.image codec bridge (spec: plans/exfiltration-app-content.md)
Design tokens (DTCG)🚧 Colour shippedBrand primitives as canonical W3C Design Tokens (DTCG) - the format Penpot imports/exports. Done: colour tokens (suse/tokens/brand), host.tokens bridge, picker swatches + reference-linked values (spec: docs/design-tokens.md). Next: dimension/type tokens, Penpot import/export, user tokens in the transfer bundle (tokens.json)
MCP agent endpoint (render)✅ DoneAn MCP server exposes the catalogue + render path as callable tools (lolly_list_tools / describe_tool / build_url / render / transform) so any agent can produce finished, rule-bound assets - add it to any MCP client as a custom connector (OAuth 2.1) or point a CLI/HTTP client at it with a bearer token. Live at mcp.lolly.tools (full endpoint: raster/PDF/animation/video via a hosted headless browser) and lolly.tools/api/mcp (serverless browser-free tier). Distinct from the Penpot authoring MCP below, which is about tool creation (spec: plans/mcp-server.md; guide: docs/mcp.md + docs/ai-agents.md)
Penpot file ingest as tools2027+Import a Penpot file and surface it as a Lolly tool (declarative, constraint-first), turning designs authored in Penpot into deterministic generators
MCP + Penpot extension (online-only authoring)2027+A Penpot MCP server articulates new tools with AI - the most visual way to create deterministic templates: a brand-informed first round, perfected with a human in the loop, targeting one-shot new contexts over time. Tool creation is online-only; the tools it produces run anywhere
RBAC + SUSE ID2027+Gate specific tools behind SUSE ID; multi-device saved state; Google Drive ingest/export

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.