Lolly
アプリを開く

Lollyのアーキテクチャ

signed by Lollyoverview.htmlHTMLpixels, not shapes12 KBAI generatedgenerated by ClaudeCheck it yourself署名済みファイルを取得

Lollyアイコン - 大きな緑と白のロリポップキャンディ

このページは、Lollyのコードを変更する人向けです。エンジン、シェル、ツールパックがどう組み合わさるか、それぞれがリポジトリのどこにあるか、あらゆる変更を縛る取り決めを説明します。Lollyが何をし、誰が使うのかを知りたい場合は、まずLollyの仕組みをお読みください。

さらに深く知るための補足が2つあります。engine/README.mdは、コードから生成されたエンジンのモジュールごとの地図です。脅威モデルと信頼境界は、同じアーキテクチャを信頼境界として読み解いたもので、エンジンが何を信頼できないものとして扱うかについての疑問に答えるページです。

全体像

最上位のツールはデータです。中間のエンジンはどのプラットフォームも知りません。最下位のシェルは1つの契約を実装し、カタログが内容を供給します。

ツールは共有エンジンにマニフェスト、テンプレート、フックを提供する。Web、Tauri、ターミナルのシェルはHostV1ケイパビリティブリッジを実装し、カタログからツールとアセットを読み込む。ツールは共有エンジンにマニフェスト、テンプレート、フックを提供する。Web、Tauri、ターミナルのシェルはHostV1ケイパビリティブリッジを実装し、カタログからツールとアセットを読み込む。Open this in Diagram Builder

純粋なデータとHandlebarsで記述できるなら、それはエンジンに属します。DOM、ファイルシステム、ネットワーク、あるいはブラウザやオペレーティングシステムのAPIに触れるなら、それはホスト、つまりブリッジのシェル側に属します。エンジンはオープンソースであり、特定のブランドやプラットフォームを知るものはエンジンに入りません。

リポジトリの構成

Lollyは1つのリポジトリです。以下のディレクトリはすべて、その中の通常のディレクトリですが、brands/suseは例外で、公開クローンが取得しない、SUSEのツールパックとカタログを収めた非公開のgitサブモジュールです。ビルドがどのパックを読むかはコンテンツプロファイル(profiles.json)で決まり、グローバルに切り替えるのではなく、プロセスごとに解決されます。クローンのコマンドと、非公開パックの中の変更をコミットする方法は、ビルドガイドにあります。

ディレクトリ収めるもの
engine/プラットフォームに依存しないコア: ローダー、ランタイム、入力、テンプレート、URLモード、単位、形式モジュール
shells/Webアプリ、Tauriのデスクトップ・モバイルアプリ、CLI、TUI、ブラウザ拡張機能
packages/ツール作者向けSDK(@lolly-tools/core)、共有のNodeシェル部品、ドキュメントレンダラーなど
community/ブランドに依存しないツールと、共有の絵文字パック
brands/ブランドパック: lolly-demo(lolly.toolsが使うデザインシステムのLolly Demo)、lolly-start(空のスターター)、非公開のsuse
services/任意のMCP、証明書、Penpotのサービスと、それらが共有するサーバーコード
schemas/ツールマニフェスト、アセットエントリ、アセット参照のJSON Schema
scripts/、tests/、docs/、api/ビルドとカタログのスクリプト、エンジンのテストスイート、これらのドキュメント、生成されたサーバーレスバンドル
最初に目にするエンジンとシェルのファイル
engine/src/
  index.ts          public surface: loader, runtime, template, inputs, url-mode
  loader.ts         fetches and validates tool files
  runtime.ts        orchestrates one mounted tool, from load to export
  template.ts       Handlebars hydration and annotateTemplate
  inputs.ts         manifest to runtime input model
  url-mode.ts       URL to input state and back
  validate.ts       JSON Schema validation of manifests
  compose.ts        nested tool renders (composes)
  embed.ts          portable lolly.tools embed URLs
  bridge/host-v1.ts type re-export of the @lolly-tools/core contract

shells/web/src/
  main.ts           boot and routing
  bridge/           the web implementations of the host APIs
  catalog/sync.ts   boot-time catalog sync and offline cache
  views/            the gallery, tool, projects, free canvas and the other views
  pro/              Batch (#/batch)

shells/cli/         bin/lolly.ts (entry), src/run.ts (headless render), src/bridge.ts
shells/tui/         src/main.tsx (the full-screen app), src/bridge.ts

リポジトリのルートにtools/やcatalog/ディレクトリはありません。packages/node-shell/src/content-roots.tsが、profiles.jsonから、問われたときに「ツール<id>はどこにあるか」「カタログはどこにあるか」に答えます。実際のtools/とcatalog/の組が書き込まれるのは、dist/、RPMのペイロード、コンテナイメージなどのビルド出力だけです。ブラウザがその2つのパスをHTTPで取得するためです。

Lollyが動作する場所

どの環境も、同じエンジンと同じツールファイルを使います。

デスクトップの分割ビュー: 左にマニフェストから生成されたコントロール、右にライブキャンバス signed by Lollyvector SVG131 paths~6.1k nodes158 groups1 image142 KBCheck it yourself署名済みファイルを取得

同じツールをスマートフォンの幅で表示しても、別のレイアウトは要りません。コントロールが上部のシートになり、プレビューが画面を占めます。

幅430pxの画面のオーディオグラム: 上にコントロールのシート、下に完成した正方形のアートワーク、そしてフローティングの書き出しピル signed by Lollyvector SVG97 paths~3.3k nodes113 groups99 KBCheck it yourself署名済みファイルを取得幅430pxの画面のオーディオグラム: 上にコントロールのシート、下に完成した正方形のアートワーク、そしてフローティングの書き出しピル signed by Lollyvector SVG97 paths~3.3k nodes113 groups100 KBCheck it yourself署名済みファイルを取得

Webアプリには、Batch(#/batch、shells/web/src/pro/)も同梱されています。1つまたは複数のツールにわたって多数の行を一度にレンダリングする、スプレッドシート形式のグリッドで、BatchについてはBatchとMulti-editで扱っています。Settings → Capabilitiesには、プラットフォームができると宣言しているすべてのことの地図があります。

新規インストール時のBatch: ツールを待つ空の行が1つあり、データが届く前にスプレッドシート画面とRenderボタンが用意されている signed by Lollyvector SVG133 paths~15k nodes185 groups1 image195 KBCheck it yourself署名済みファイルを取得新規インストール時のBatch: ツールを待つ空の行が1つあり、データが届く前にスプレッドシート画面とRenderボタンが用意されている signed by Lollyvector SVG133 paths~15k nodes185 groups1 image195 KBCheck it yourself署名済みファイルを取得

アーキテクチャ上のコミットメント

これらの決定は確定済みです。コードベースのあらゆる他の決定を形作っているため、どれを変更するにも大きな取り組みが必要です。

1. 宣言的なツール、命令的な逃げ道付き

ツールは、マニフェスト(tool.json)、テンプレート(template.html)、そして任意のhooks.jsです。マニフェストが入力を宣言し、テンプレートは宣言しません。入力はHandlebarsのトークンから推測されません。ほとんどのツールはマニフェストとテンプレートだけで、そのおかげで開発者でない人も作成できます。

Street Mapのコントロール群: 都市のドロップダウン、テーマの選択、太さのスライダー、色のトリガー。どれもマニフェストの1行から描かれている signed by Lollyvector SVG39 paths~2.6k nodes54 groups2 images43 KBCheck it yourself署名済みファイルを取得Street Mapのコントロール群: 都市のドロップダウン、テーマの選択、太さのスライダー、色のトリガー。どれもマニフェストの1行から描かれている signed by Lollyvector SVG39 paths~2.6k nodes54 groups2 images43 KBCheck it yourself署名済みファイルを取得

QRのエンコードやグラフの整形など、値を計算するツールにはフックを追加します。これは、ケイパビリティブリッジが注入された状態でランタイムが呼び出す、名前付きのライフサイクル関数です。フックはセキュリティのサンドボックスではなく移植性の契約であり、時間の予算の範囲内で実行されます。フックがページのグローバルを使わないことが確認されたツールにはisolate: trueが付き、Workerで実行されます。ライフサイクルと予算はツールのフックに、ページ内の実行機構が何を封じ込め、何を封じ込めないかは脅威モデルに書かれています。

2. ツールとアセットはバンドルされたコードではなくデータである

WebアプリとTauriアプリは、起動時にツールとアセットのカタログを取得してキャッシュし、そこにあるもので動作するため、新しいツールやアセットのためにアプリのリリースは不要です。アセットのバイト列にはSHA-256のチェックサムが付き、アセットのidとversionがキャッシュの無効化を左右します。

3. ケイパビリティブリッジがツールの見る唯一のAPI

ツールは、テンプレートの外のDOMに触れず、fetchを直接呼ばず、ファイルシステムも読みません。バージョン付きのhost.*メソッドを呼び出します。契約の正式な定義は、ツール作者向けSDKの@lolly-tools/coreであるpackages/core/src/host-v1.tsにあり、第三者がエンジンに依存せずにこれに対してビルドできます。engine/src/bridge/host-v1.tsはその型を再エクスポートします。

必須API内容
host.profileユーザーの詳細。bindToProfileを通じて入力を自動入力する
host.assetsカタログのクエリ、アセットの解決、ホストのピッカー
host.state入力スロットの保存と読み込み: WebではIndexedDB、Tauriではファイルシステム、CLIではメモリ
host.clipboardテキストまたは画像をクリップボードに書き込む(プラットフォームごとの代替付き)
host.exportレンダリング対象のラスタライズまたはシリアライズと、デバイス上のファイル変換経路
host.logホストへのレベル付きログ

それ以外のAPIはすべて任意で追加的なものです。tokens、text、color、geom、pdf、compose、media、recorderなど多数があります。ツールのマニフェストは、フックが確認せずに呼び出す任意のAPIをrequiresに列挙し、それを欠くホストはツールのマウントを拒否します。残りについては、フックが機能を検出します。recorderなど一部は、ツールが宣言するデバイスのケイパビリティも必要とします。packages/core/src/host-v1/apis.tsにすべてが一覧されており、それぞれはHost APIに記載されています。メソッドの追加はマイナーバージョン、削除や変更はメジャーバージョンであり、v2が出てもv1は動作し続けます。

4. アセットIDは永久

lolly/logo/primaryは契約です。一度公開されたIDは変更されず、再利用もされません。バイト列の変更ではアセットエントリのversionが上がり、置き換えではdeprecated: trueと、必要に応じてreplacedByが設定されるため、既存の参照は常に解決されます。これが、保存したセッションと共有リンクが何年も動き続ける理由です。

5. URLモードはファーストクラスである

すべての入力はURLパラメーターとして表現できるため、リンクは完成したファイルです。

https://lolly.tools/t/qr-code?url=https://example.com&ecl=H

このリンクだけで、ほかに何もなくても、完成したファイルになる signed by Lollyvector SVG55 paths~2.6k nodes38 groups60 KBCheck it yourself署名済みファイルを取得このリンクだけで、ほかに何もなくても、完成したファイルになる signed by Lollyvector SVG55 paths~2.6k nodes38 groups61 KBCheck it yourself署名済みファイルを取得

パラメーターを変えると、別のファイルになります。このパレット全体は、1つのシード色、ハーモニー、ステップ数からできています。

4色相にわたる9段階、すべてリンクに含まれる単一のシード色から生成されています signed by Lollyvector SVG10 groups25 KBCheck it yourself署名済みファイルを取得4色相にわたる9段階、すべてリンクに含まれる単一のシード色から生成されています signed by Lollyvector SVG10 groups25 KBCheck it yourself署名済みファイルを取得

CLIは、引数から同じURL状態を作って同じパイプラインを実行するため、CLIがアプリとずれることはありません。engine/src/url-mode.tsが解析とシリアライズを行い、予約された名前(format、width、dpi、c2pa、zなど)はツールに渡されません。すべては予約済みURLパラメーターに一覧があります。アセット入力はidで渡され、テンプレートが埋められる前にhost.assets.get()で解決されます。

6. ストレージはブリッジを通る

WebアプリはIndexedDB、Tauriはファイルシステム、CLIはメモリを使います。ツールに見えるのはhost.state.save(slot, data)とhost.state.load(slot)だけで、localStorageは使いません。アカウントなしで、ツールごとに名前付きの複数のセッションを保持できます。ブリッジが唯一の接点であるため、その状態は可搬でもあります。shells/web/src/data-transfer.tsがそれを1つのlolly-backupファイルに読み出し、他のどのインストールにもインポートできます(データ移行)。同期は、同じバンドルを利用者が選んだストレージに送ります。

7. 成熟度タグは、ツールが何であるかを示す

すべてのツールはstatus: official | community | experimentalを宣言します。ギャラリーはステータス順に並び、experimentalのツールは既定で書き出しに透かしを入れます。ランタイムは、呼び出し側がwatermark: falseを渡さないかぎり透かしをオンにし、デバイス上で動作するツールは対象外です。チームがカタログの周りにどんなプロセスを置いても、それはこの上に載ります。

8. 入力はマニフェストで型付けされる

入力はtypeを宣言します。text、longtext、number、boolean、color、select、asset、date、time、datetime-local、url、blocks、vector、table、fileのいずれかです。ホストは型ごとにコントロールを描画するため、ツールはコントロールのコードを書かず、どの入力にもbindToProfileを付けられます。特に重要なものが3つあります。

9. テンプレートにロジックはない

テンプレートはHandlebarsで、開発者でない人にも書けるように選ばれています。{{x}}はエスケープされ、{{{x}}}は明示的なオプトインです。ロジックはhooks.jsにあり、明示的で、レビューできます。多くのテンプレートは、フックが計算したマークアップに{{{を使うため、各フックは渡すものをエスケープしなければなりません。ヘルパーはdefault、upper、lower、eq、markdown、asset、framing、mediaに加え、.ics、.vcf、.csvの兄弟テンプレート向けのicsStamp、rfcText、csvCell、arrowです。これらはテンプレートとレンダリングで扱っています。

10. ツールはツールを合成する

ツールは、ツール同士のインポートなしに、他のツールのレンダリングを埋め込めます。合成はエンジンが解決します。マニフェストはcomposes: [{ id, tool, inputs }]を宣言して子を{{asset <id>}}で配置でき、ポータブルな埋め込みURL(https://lolly.tools/tool/<id>.svg?…)が子をローカルでレンダリングします。DesignがライブのQRコードやグラフを配置するのは、この仕組みです。SVGの子は、SVGまたはPDFの親の中でもベクターのままです。子は中間生成物であり、透かしも署名も付かず、子をレンダリングできないシェルはそのスロットを空にしますが、親は引き続きレンダリングされます。詳細は合成とブランドオーバーレイにあります。

あえてやらないこと

1回のレンダリングのライフサイクル

誰かがhttps://lolly.tools/t/qr-code?url=https://example.com&ecl=Hを開きます。

  1. 起動。 WebアプリはIndexedDBを開き、ケイパビリティブリッジを構築し、ツールとアセットのカタログを同期するか、オフラインならキャッシュから読み込みます。
  2. ルーティング。 パスがqr-codeのツールビューを選び、クエリがその初期値になります。
  3. 読み込み。 loadToolがtool.jsonを取得してスキーマで検証し、テンプレート、スタイル、フックを取得します。
  4. リンクの解析。 parseUrlStateがパラメーターを入力値に変換します。?logo=lolly/logo/primaryのようなアセット参照は、未解決の{ id }になります。
  5. ランタイムの開始。 createRuntime(tool, host, initialValues)が、プロフィールデータ、既定値、リンクから入力モデルを構築し、アセット参照を解決し、フックを読み込んでonInitを呼び出します。
  6. レンダリング。 シェルはランタイムを購読し、モデルからコントロールを描画し、埋めたテンプレートを#tool-canvasに書き込みます。
  7. 操作。 入力するとruntime.setInputが呼ばれ、制約を適用し、onInputを呼び出し、テンプレートを再び埋めて再描画します。
  8. 書き出し。 Downloadがruntime.export(node, format)を呼び、シェルのhost.exportがファイルを生成します。ラスター、DOMウォーカーによるベクター、PDF、ドキュメント、schemas/tool.schema.jsonにあるその他の形式のいずれかです。物理単位は形式ごとに変換され、メタデータとContent Credentialsは、形式が保持できる場合に埋め込まれます。experimentalのツールの透かしは、ツールではなくホストが付けます。書き出しの手順はテンプレートとレンダリングとファイルを書き出すで扱っています。

?optionsで開く書き出しパネル: ファイル名と形式の組、出力サイズ、ファイルを書き出すコントロール signed by Lollyvector SVG77 paths~6.0k nodes87 groups3 images88 KBCheck it yourself署名済みファイルを取得?optionsで開く書き出しパネル: ファイル名と形式の組、出力サイズ、ファイルを書き出すコントロール signed by Lollyvector SVG77 paths~6.0k nodes87 groups3 images88 KBCheck it yourself署名済みファイルを取得

同じライフサイクルは、Tauriでも、ヘッドレスのDOMを使うCLIでも動作し、出力はファイルまたは標準出力に送られます。

ライセンス

engine/、shells/、services/、schemas/、ドキュメントジェネレーターを含むコードは、1つの公開リポジトリlolly-tools/lollyでMPL-2.0です。ドキュメントの文章はCC BY-SA 4.0です。ブランドパックは、各パックのNOTICE.mdに独自の条件を持ちます。コミュニティツールはMPL-2.0で、非公開のSUSEパックはSUSEの専有です。ライセンスに関する全文の注記は概要にあります。