# コミュニティのスキンとパック

FolderSkinを使うすべての人と、誰でも無料でスキンを共有できます。共有するスキンのまとまりを**パック**と呼び、スキン1つだけでも1つのパックになります。パックは専用のリポジトリ[folderskin-community](https://github.com/prajwal-svm/folderskin-community)の`packs/`に置かれていて、追加するのにアカウントは要りません。FolderSkinには最初から入っているスキンがありません。スキンの出どころは、パック、自分の画像、AIの生成結果のいずれかです。

## パックを追加する

FolderSkinを初めて開くと、ライブラリの手始めになるパックが表示されます。それ以降は、アプリで**コミュニティ**を開いてください。上部のフィルタは、パックに付いているタグです。**追加**を押すとパックのスキンがライブラリに入り、パックのタグも一緒に付きます。各スキンの⋯メニューには、どのパックから来たか、誰が共有したかが表示されます。**削除**を押すと、パック全体をまた取り除けます。そのパックのスキンをすでに使っているフォルダでは、アイコンはそのまま残ります。アイコンはフォルダそのものに保存されているからです。

**フォルダから追加**は、コンピュータ上にあるパックのフォルダで同じことを行います。共有する前にパックを試してみるときも、この方法を使います。

[folderskin.app](https://folderskin.app/ja/community/)のギャラリーでは、どのパックにも**インストール**ボタンがあります。押すとFolderSkinが開き、コミュニティでそのパックを表示して追加します。パックの**追加**ボタンを押したときとまったく同じです（下の[インストールリンク](https://folderskin.app/ja/docs/packs/#インストールリンク)を参照）。**公式**の印が付いたパックは、メンテナのお墨付きです。

パックは丸ごと追加されるか、まったく追加されないかのどちらかです。まずすべての画像をダウンロードして検査し、そのあとで一度にまとめて保存するので、接続が切れたりディスクがいっぱいになったりしても、パックが中途半端にライブラリに残ることはありません。スキンはパックで決められた順に並びます。

## 自分のスキンを共有する

共有はアプリから行います。FolderSkinがパックをコミュニティサービス（`community.folderskin.app`）に送り、そこでメンテナが審査します。GitHubのアカウントは要りません。FolderSkin 0.1.6以前では、GitHubにプルリクエストを作成する方法も選べましたが、0.1.7でこの方法はなくなりました。

1. 共有したいスキンにタグを付けます（⋯ → タグ）。スキンを1つだけ共有するなら、⋯ → **コミュニティで共有**を使います。複数を共有するなら、**コミュニティ → スキンを共有**を開いてタグを選びます。
2. パックの名前、タグ、ライセンスを入力し、画像の出どころを書いて、自分が共有してよい画像であることを確認するチェックを入れます。
3. 初回だけ、パックの作者として表示される名前で、FolderSkinがブラウザ上でこのコンピュータを確認します。確認はコンピュータごとに1回だけです。
4. 送信します。FolderSkinは、何かがコンピュータの外に出る前に、パックが下記の仕様に合っているかどうかを検査します。**送信履歴**には送ったパックが一覧で表示され、却下されたものはその理由もわかります。

画像はすべてロスレスで共有されるので、完成したフォルダの透明なふちまで含めて、作ったとおりの見た目で届きます。FolderSkinは送信前に各画像をロスレスWebPに変換します。1枚につき数秒かかり、変換が終わった枚数を数えながら進みます。1024pxでは1.5MBに収まらないほど細かい画像は、ロスレスのまま896px、それでも無理なら768pxにして、どの画像を小さくしたかを知らせます。パックの画像は合計64MBまでです。それを超えるパックは受け付けられず、2つのパックに分けるよう提案されます。

どのパックも、ほかの人の目に触れる前に人が確認します。承認されると、15分ほどで自動的に公開されます（下の[承認で公開されるしくみ](https://folderskin.app/ja/docs/packs/#承認で公開されるしくみ)を参照）。同じ名前のパックがいくつあってもかまいません。選んだ名前がそのまま全員に表示され、パックにはそれとは別に固有のIDが付きます（[パックのID](https://folderskin.app/ja/docs/packs/#パックのid)）。

パックは手作業でも追加できます。[folderskin-community](https://github.com/prajwal-svm/folderskin-community)に、`packs/`の下へフォルダを1つ追加するプルリクエストを送ってください。フォルダは`packs make`で作ります（[画像からパックを作る](https://folderskin.app/ja/docs/packs/#画像からパックを作る)）。そうすると生成されたIDが付き、プルリクエストではアプリと同じチェックが実行されます。アプリの**フォルダとして保存**でも、下記のルールをすべて満たすパックのフォルダを書き出せます。

## パックのID

パックにはそれぞれIDがあります。IDはパックのフォルダ名であり、パックへのリンクにも必ず含まれます。IDはパックを作るときに一度だけ、名前とランダムな6文字から作られます。たとえばClassic Artというパックなら、`classic-art-k7q2mx`のようなIDになります。名前は自由に重複してよいので、Classic Artという名前のパックが100個あってもかまいません。一意でなければならないのはIDだけです。手作業で作るパックのIDは`packs make`が、アプリから共有されたパックのIDはコミュニティサービスが作ります。一度決まったIDは、パックの名前が変わっても変わりません。

ランダムな部分は、`a`から`z`までと`2`から`7`までの文字を使った6文字で、システムの安全な乱数源から生成されます。新しいIDが、`packs/`にあるフォルダの名前や、`moved.json`にある古いIDと重なることはありません。

### moved.json

IDが生成されるようになる前に作られたパックは、`classic-art`のように名前だけから作ったIDを持っていました。これらのパックには`packs rename`で生成したIDが付け直され、`packs/`の隣にある`moved.json`に、古いIDと現在のIDの対応が記録されています。

```json
{ "version": 1, "moved": { "classic-art": "classic-art-k7q2mx" } }
```

- 古いIDは、`packs/`のどのフォルダも使っていないパックIDです。新しいパックに与えられることもありません。
- 新しいIDは、どれも`packs/`にあるパックを指します。別の古いIDを指すことはありません。パックをもう一度リネームすると、それまでそのパックを指していたものがすべて最新のIDを指すように書き換えられます。
- `packs index`と`packs catalog`は、この対応表を`"moved"`として`index.json`と`head.json`にコピーします。これにより、アプリ、Webサイト、コミュニティサービスが古いIDからパックをたどれます。0.1.7以降のアプリは、古いIDで追加したパックを新しいIDに移します。古いIDのインストールリンクからでも、目的のパックが見つかります。
- リネームしたパックも、最初に公開された日付を保ちます。`packs index`は、そのパックのいずれかのIDを追加した最も古いコミットの日付を使います。
- `pack.json`には何も追加しません。`pack.json`は仕様にないフィールドを受け付けず、追加するとアプリ0.1.4〜0.1.6がそのパックを拒否してしまいます。

### packs rename

```sh
cargo run -p folderskin-tools -- packs rename --dir ../folderskin-community --all
cargo run -p folderskin-tools -- packs rename --dir ../folderskin-community classic-art
cargo run -p folderskin-tools -- packs rename --dir ../folderskin-community classic-art --to classic-art-k7q2mx
```

folderskin-communityのgitチェックアウトの中で、パックに生成したIDを付けます。各フォルダは`git mv`で移動するので、履歴も一緒に引き継がれます。`featured.json`と`official.json`は順序を保ったまま新しいIDに書き換えられ、すべての移動が`moved.json`に記録されます（なければ作成されます）。変更はすべてステージされ、そのままコミットできる状態になります。

`--all`は、IDが生成されたものではないパックをすべてリネームし、それ以外はそのままにします。判定はIDの形だけで行います。そのため、`data-structures-in-bricks`のように最後の単語がたまたま6文字の古いIDは、生成されたIDに見えてしまいます。こうしたパックは、IDを指定してリネームしてください。IDを指定したパックは、現在のIDが何であれ、新しく生成されたIDに移ります。`--to`を付けた場合はそのIDに移りますが、これは生成された形式のIDで、どのパックも今も過去も使っていないものでなければなりません。もう一度実行しても何も変わりません。移動済みのパックは`moved.json`で見つかり、すでに移動済みと表示されます。

## 承認で公開されるしくみ

パックは承認されると公開されます。誰かが手作業でファイルをコピーすることはありません。

1. メンテナがパックを承認すると、コミュニティサービスがパックにIDを付けます。サービスがGitHubのトークン（`GITHUB_DISPATCH_TOKEN`）を持っている場合は、`pack-approved`のrepository dispatchで、folderskin-communityのPacksワークフローもすぐに開始します。
2. トークンがない場合は、ワークフローが自分でパックを見つけます。15分ごとに`https://community.folderskin.app/v1/exports/pending`へ、承認済みで公開を待っているパックの数を問い合わせ（数秒で済みます）、待っているパックがあるときだけ残りの処理を行います。また、待っているパックの有無にかかわらず毎日と毎週実行されるほか、メンテナが手動で開始したときにも実行されます。
3. `community pull --no-done`が承認済みの各パックを`packs/`に書き込み、すべてのファイルのサイズとSHA-256を、アップロード時にサービスが記録した値と照合します。サービスが付けたIDは生成された形式でなければならず、すでにあるフォルダが上書きされたり、番号を付けて別名にされたりすることはありません。パックの完成したフォルダは、チェックの前に形をそろえます（[パックのフォルダの形をそろえる](https://folderskin.app/ja/docs/packs/#パックのフォルダの形をそろえる)）。その形から離れすぎているフォルダはそのまま残し、実行ログに警告を出します。
4. `packs check`が、プルリクエストのときと同じようにすべてのパックをチェックします。
5. 各パックはgithub-actions[bot]によって`Add the <name> pack`というメッセージでコミットされ、`main`にプッシュされます。
6. そのあとで初めて、`community done`がサービスにパックの公開を伝えます。チェックやプッシュに失敗したパックはサービス側で待機したままになり、次の実行でもう一度試されます。
7. 同じ実行の中で、`index.json`、プレビュー、`v2/`を作り直し、`v2/`をミラーにコピーして（[後述](https://folderskin.app/ja/docs/packs/#ミラー)）、それらをコミットします。ワークフロー自身のトークンで行ったプッシュでは別のワークフローが起動しないため、すべてを1回の実行で行っています。

つまりパックは、承認から15分ほどで公開されます。サービスがワークフローを直接開始した場合は、数分で公開されます。失敗した実行では何も公開されず、ワークフローが失敗したことをGitHubがメンテナにメールで知らせます。GitHubは混雑しているときにスケジュール実行を遅らせることがあり、60日間動きのないリポジトリではスケジュールを止めてしまいます。repository dispatchは、このどちらの影響も受けません。

ワークフローには、メンテナの署名鍵（`community keygen`が書き出したファイル全体）が、リポジトリシークレット`FOLDERSKIN_ADMIN_KEY`として必要です。これがないと承認済みのパックはサービスで待機したままになり、実行ログにもそう表示されます。リポジトリ変数`REQUIRE_GENERATED_IDS`を`true`にすると、生成されたIDを持たないパックはすべてのチェックで拒否されます。

手作業でのプルも引き続き使えます。`community pull`はパックを書き込むと、すぐにサービスへ伝えます。実行した人が書き込まれた内容をコミットする前提だからです。`--no-done --pulled pulled.json`を付けると、`community done --from pulled.json`を実行するまでサービスへの通知を保留します。ワークフローはこの方法で実行しています。サービスに2回伝えても問題はありません。

### ミラー

アプリは、ツリー全体を`https://packs.folderskin.app`から読み込めます。これはリポジトリと同じ`v2/`を保持するCloudflare R2のバケットです。バケットに書き込むのはコミュニティサービスなので、ワークフロー自体はCloudflareのトークンを必要としません。`community mirror`が各ファイルにメンテナの鍵で署名し、サービス経由で送ります。

```sh
cargo run -p folderskin-tools -- community mirror --tree ../folderskin-community \
  --public https://packs.folderskin.app --api https://community.folderskin.app --key ~/folderskin-admin.key
```

すべてのファイルについてHEADリクエストでミラーに問い合わせ、同じ長さですでに配信されているファイルはそのままにします。`head.json`以外のファイル名は、中身のハッシュだからです。残りのファイルは`PUT /v1/admin/tree/<path>`でアップロードし、バケット側で検証できるよう、それぞれのSHA-256を`X-Content-SHA256`に付けます。`head.json`は最後に、ほかのファイルがすべてそろってから送るので、ミラーが持っていないカタログを指すことはありません。時間をおけば通る可能性のあるリクエスト（応答なし、5xx、429）は、待ち時間を1秒から倍々に延ばしながら合計5回まで試し、`Retry-After`には30秒まで従います。ワークフローはコミットの前にこれを実行するので、GitHub上の`head.json`が更新されるのは、そこに書かれたものがすべてミラーにそろってからです。ミラーはリポジトリ変数`COMMUNITY_MIRROR_URL`で有効になり、`head.json`にミラーが載るのは有効な間だけです。

## パックの仕様

パックは1つのフォルダです。

```
packs/night-prints-h4x2qe/
  pack.json
  koi.webp
  fox-in-the-rain.webp
```

`pack.json`は次のとおりです。

```json
{
  "version": 1,
  "name": "Night prints",
  "author": "your-github-name",
  "license": "CC0-1.0",
  "tags": ["woodblock", "night"],
  "skins": [
    { "file": "koi.webp", "name": "Koi over the wave", "tags": ["animals"] },
    { "file": "fox-in-the-rain.webp", "name": "Fox in the rain" }
  ]
}
```

| フィールド | ルール |
|---|---|
| `version` | `1` |
| `name` | 1〜40文字 |
| `author` | GitHubのユーザ名 |
| `license` | `CC0-1.0`、`CC-BY-4.0`、`MIT`のいずれか |
| `tags` | タグ1〜5個。パックのすべてのスキンに付き、最初のタグがみんなのフィルタでそのパックを表す名前になります |
| `skins` | 1〜50項目 |
| `skins[].file` | フォルダ内の画像 |
| `skins[].name` | 1〜60文字 |
| `skins[].tags` | 任意。そのスキンだけに最大3個まで追加できます |

ほかのフィールドは使えません。そのため`"tag"`のような打ち間違いは、無視されずにチェックで失敗します。

### 制限

| | 制限 |
|---|---|
| パック内のスキン | 1〜50個 |
| 各画像 | ロスレス（PNGまたはロスレスWebP）。最大1.5MB |
| パックの画像の合計 | 最大64MB |
| 画像の辺の長さ | 256〜1024px |
| ファイル名 | 英字、数字、`.`、`-`、`_`のみで、`.png`か`.webp`で終わる |
| ID（フォルダ名） | 名前とランダムな6文字。例：`night-prints-h4x2qe`（[パックのID](https://folderskin.app/ja/docs/packs/#パックのid)）。小文字の英字と数字でできた単語を1つのハイフンでつなぎ、最大40文字 |
| タグ | 小文字の英字、数字、スペース、ハイフンで、最大24文字 |
| `pack.json` | 最大64KB |

50個と64MBにしている理由は、パックがテーマのあるセットで、追加する人は全体をダウンロードするからです。スキン50個なら審査もすぐに済み、64MBあれば1枚1.3MBの画像が50枚、最大サイズの画像なら42枚収まります。

FolderSkin 0.1.7より前に公開されたパックは、PNG、JPEG、または任意のWebPで1枚2MBまでという基準で作られていて、アプリは今も読み込めます。`packs make`で作り直せば、上のルールに従うようになります。`packs check`に`--require-lossless`を付けると、パックをこのルールでチェックします（[パックを自分でチェックする](https://folderskin.app/ja/docs/packs/#パックを自分でチェックする)）。コミュニティサービスは、このルールを満たすパックしか受け付けません。

### 画像

画像は2種類のどちらかで、ウィンドウにドロップした画像と同じ方法で見分けます。

- **完成したフォルダ**：透明な背景、または単色のマゼンタ`#FF00FF`の背景に描かれたもの。マゼンタはFolderSkinが切り取ります。描かれたとおりにそのままアイコンになります。
- **それ以外**：FolderSkinのフォルダにはめ込まれます。フォルダが画像のどこを切り取るかは[SKINS.md](https://folderskin.app/ja/docs/skins/)で説明しているので、主題が切れないようにできます。

3つのOSのどれでも、描かれるアイコンの最大サイズは1024pxなので、それより大きな画像にしても意味はありません。

画像はすべてロスレスなので、パックは作ったとおりの見た目になります。グラデーションにブロックノイズが出ることも、文字のまわりにリンギングが出ることもなく、完成したフォルダのふちも描いたときのままきれいです。ロスレスWebPは同じ画像のPNGより3分の1ほど小さいので、アプリも`packs make`もWebPで書き出します。細かく描き込んだ1024pxの画像は0.6〜1.5MBほどで、ほとんどは800KB前後です。1.5MBに収まらない画像は、ぼかして押し込むのではなく、ロスレスのまま896px、さらに768pxと小さくします。

### パックのフォルダの形をそろえる

FolderSkinは、完成したフォルダの画像全体をアイコンに収めます。1枚ずつ作ったフォルダは、それぞれ自分の輪郭ぎりぎりで切り抜かれていて、縦横の比率がまったく同じレンダリングは2つとありません。あるパックでは、幅が高さの1.03倍から1.30倍までばらついていました。Finderで並べると、ずんぐりしたものは背の高いものより小さく見えます。そこで、パック内の完成したフォルダはひとつの形にそろえます。

- **パックの形**は、各フォルダの形の中央値です。フォルダは、画像のうち不透明度が半分を超える部分の幅÷高さで測るので、やわらかいふちやうっすらした影は数に入りません。
- **各フォルダをその形で正確に描き直します**。フォルダの部分で切り抜き、幅962px（1024pxのテンプレートでのFolderSkin自身のフォルダの幅で、`folderskin-tools template`で確認できます）、高さはその形から決まる値にリサイズします。それを透明な1024 × 1024の画像の中で、テンプレートのフォルダと同じ位置、つまり同じ左端と同じベースラインに置き、ロスレスWebPで保存します。PNGは同じ名前の`.webp`になり、`pack.json`もそれに合わせて更新されます。
- **形を変えるのは最大8%までです**。その程度の変化なら誰も気づきません。それ以上の変形が必要なフォルダは*外れ値*です。そこまで引き伸ばすと文字や顔がつぶれて見えるので、形は変えません。外れ値をどう扱うかはコマンドによって異なり、最終的には人が判断します。
- **アートワークには手を加えません**。FolderSkinが自分のフォルダにはめ込むので、そろえるべき形がそもそもありません。完成したフォルダが1つしかないパックも同様です。

この処理は、`packs make`が作るすべてのパック、`community pull`が取り込むすべてのパックに対して行われ、`packs/`にある既存のパックには`packs normalize`で行います。2回実行しても何も変わりません。すでにパックの形になっていて正しい位置にあるフォルダは描き直されず、書き込む内容とすでに同じファイルは書き換えられません。

| コマンド | 外れ値の扱い |
|---|---|
| `packs make` | パックから外し、一覧に表示します。`--keep-outliers`を付けると、そのまま残します |
| `packs normalize` | 一覧に表示し、そのまま残します。`--drop-outliers`を付けると`pack.json`から外し、画像も削除します |
| `community pull` | そのまま残し、警告を出力します（Packsワークフローのログに表示されます）。誰かが共有したスキンが、人の判断なしに外されることはありません |

```sh
cargo run -p folderskin-tools -- packs normalize --dir ../folderskin-community
cargo run -p folderskin-tools -- packs normalize --dir ../folderskin-community classic-art-5rxas2 --tolerance 0.3
cargo run -p folderskin-tools -- packs normalize --dir ../folderskin-community dreamscapes-ppfia6 --drop-outliers
```

`packs normalize`は、すべてのパック、または指定したパックを順に処理します。パックごとに形と描き直したものを表示し、外れ値はどれだけずれているかとともにすべて挙げます。`packs check`に通らないパックは、通るようになるまで手を付けません。`--tolerance`は8%の値を変更します。Classic Artのモナ・リザのように、外れ値でもパックの形に合わせたい場合に使います。`--drop-outliers`はメンテナ向けです。これ以外にスキンを削除するものはありません。`packs check --require-one-shape`は、フォルダの形が1%を超えて異なるパックを拒否します（[パックを自分でチェックする](https://folderskin.app/ja/docs/packs/#パックを自分でチェックする)）。ひとつの形に描き直したフォルダが拒否されることはありません。

### ライセンス

共有するスキンには、クリエイティブ・コモンズかMITのライセンスを使います。

- `CC0-1.0`：誰でも、どんな目的にも使えます。ほとんどのスキンはAIで作られていて、CC0は権利の主張がいちばん少ないため、これが既定値です。
- `CC-BY-4.0`：誰でも使え、使う人はあなたの名前をクレジットに表示します。
- `MIT`：誰でも使え、あなたの名前も一緒に残ります。

共有するのは、自分で作った画像か、共有を許可されている画像だけにしてください。

## 画像からパックを作る

`packs make`は、画像生成モデルから保存したレンダリング画像などが入ったフォルダを、folderskin-communityのチェックアウト内の`packs/`の下に、そのままチェックに通るパックとして作ります。以下のコマンドは、このリポジトリで、隣にfolderskin-communityをチェックアウトした状態で実行します。

```sh
cargo run -p folderskin-tools -- packs make ~/Downloads/3d-renders --dir ../folderskin-community \
  --name "3D" --tags 3d,glossy --author your-github-name --preview /tmp/3d.png
```

パックには、名前とランダムな6文字からなる`3d-k7q2mx`のような固有のIDが付き、それがフォルダ名になります。IDはレポートに表示されます。`packs/`にあるフォルダの名前や、`moved.json`にある古いIDと重なることはありません。

各画像は、アプリで画像を追加するときと同じように振り分けられます。[PROMPTS.md](https://folderskin.app/ja/docs/prompts/)のチャット用プロンプトの指示どおりマゼンタの上に描かれた完成したフォルダや、本当に透明な背景の完成したフォルダは、切り抜かれてそのままアイコンになります。それ以外は、FolderSkinのフォルダに使うアートワークになります。どの画像も1024pxに縮小され、ロスレスWebPで保存されます。アプリがパックの共有に使うエンコーダが組み込まれているので、何もインストールする必要はありません。それでも1.5MBを超える画像は896px、さらに768pxにして、そのことをレポートに表示します。`--max-kb`を使えば、1.5MBより小さく抑えられます。画像の合計が64MBを超える場合は受け付けず、2つのパックに分けるよう提案します。libwebpのもっとも丁寧な設定では1枚に数秒かかるので、すべてのコアで同時に処理します。形式について詳しくは[SKINS.md](https://folderskin.app/ja/docs/skins/#パック用の画像)にあります。各画像がどちらに振り分けられたかはレポートに表示されます。スキンの名前はファイル名から付けられるので、先にファイル名を整えておくか、あとで`pack.json`の名前を直してください。`--preview`を付けると、すべてのスキンをフォルダの形で1枚のPNGに描き出すので、まとめて確認できます。

完成したフォルダが2つ以上あれば形をそろえ（[パックのフォルダの形をそろえる](https://folderskin.app/ja/docs/packs/#パックのフォルダの形をそろえる)）、どれを描き直したかをレポートに表示します。ほかのフォルダの形から8%を超えてずれているフォルダはパックから外し、どれだけずれているかをレポートに表示します。`--keep-outliers`を付けると、外さずにそのまま残します。外れ値を残さずに作ったパックは、`packs check --require-one-shape`に通ります。

`--id`を使うと、既存のパックを新しい画像から作り直せます。`--id 3d-k7q2mx`は`packs/3d-k7q2mx/`の中身をすべて置き換え、パックのIDはそのまま残るので、このパックを追加した人全員に、新しいバージョンがアップデートとして届きます。新しいフォルダはまず別の場所で作ってチェックし、通ったときだけ古いフォルダと入れ替えます。`--id`を付けなければ、`packs make`は常に新しいパックを作ります。

画像生成モデルに`#FF00FF`を指定しても、代わりにラズベリー色やホットピンクで一様に塗ってしまうことがよくあります（Classic Artパックを作ったときのGrokもそうでした）。`--flat-backdrop`は、どんな色でも単色の背景なら切り取ります。画像ごとに背景の色を測り、ふちまでつながっている部分だけを取り除き（そのため、フォルダの中の赤いマントは残ります）、やわらかいドロップシャドウも一緒に取り除いて、ふちにピンクの縁取りが残らないよう絵の色でなじませます。無地のグレーや黒の背景では、その背景自体のノイズの範囲にとどまり、上へ広がることもないので、フォルダのふちに接する暗いコートやインクの線が背景と間違われることはありません。あとで`--preview`のシートを確認してください（明るいグレーの上に描かれるので、穴があればわかります）。単色の背景がない画像は、これまでどおりアートワークとして扱われます。

1枚の画像がアプリでどう表示されるかを見るには、`render`でフォルダとして描き出します。

```sh
cargo run -p folderskin-tools -- render ../folderskin-community/packs/3d-k7q2mx/glass.webp --out /tmp/glass.png --size 512
```

## パックを自分でチェックする

このリポジトリで、隣にfolderskin-communityをチェックアウトした状態で実行します。

```
cargo run -p folderskin-tools -- packs check --dir ../folderskin-community
```

`packs/`のすべてのフォルダをアプリと同じルールでチェックし、問題があればそれぞれ文章で表示します。folderskin-communityのチェックアウト内で実行する場合は、`--dir`を省略できます。ツールは既定で現在のフォルダを見るからです。画像は1枚2MBまで（0.1.7より前に公開されたパックの基準）、パックの画像は合計64MBまでであることをチェックします。`--require-lossless`を付けると、新しいパックが従うルール、つまりPNGまたはロスレスWebPで最大1.5MBというルールで、すべての画像をチェックします。これはコミュニティサービスが受け付け、`packs make`が書き出す形式です。以前のパックの作り直しが済むまでは、指定しない限りオフです。`--max-kb`を使うと、さらに小さいサイズに制限できます。

IDどうしの関係もチェックします。大文字と小文字しか違わない2つのフォルダ名は、macOSとWindowsでは同じフォルダになってしまいます。また、パックは`moved.json`にある古いIDを使えず、`moved.json`自体も独自のルールに従う必要があります（[moved.json](https://folderskin.app/ja/docs/packs/#movedjson)）。名前は重複してよいので、比較しません。`--require-generated-ids`は、IDが生成されたものではないパックをすべて拒否します。指定しない限りオフで、folderskin-communityのワークフローでは、変数`REQUIRE_GENERATED_IDS`が`true`のときに指定されます。

`--require-one-shape`は、完成したフォルダの形がそろっていないパック、つまり幅÷高さが1%を超えて異なるフォルダが2つあるパックを拒否します。ひとつの形に描き直したフォルダは必ずこの範囲に収まるので、一度も形をそろえていないパックや、誰かが残した外れ値を見つけられます。エラーには、最も離れた2つのフォルダの名前と、実行すべきコマンドが表示されます（[パックのフォルダの形をそろえる](https://folderskin.app/ja/docs/packs/#パックのフォルダの形をそろえる)）。指定しない限りオフで、folderskin-communityのワークフロー向けです。

## アプリがパックを読み込むしくみ

コミュニティには**リスト**表示と**ギャラリー**表示があり、パックの**表示**を押すとそのパックが開きます。何かを追加する前に、すべてのスキンがフォルダになった姿で、名前とともに表示されます。**再読み込み**で一覧を読み直します。追加したあとに変更されたパックには**アップデート**が表示され、押すとスキンが新しいバージョンに入れ替わります。フォルダのアイコンはそのまま残り、両方のバージョンに共通する画像のお気に入りも、お気に入りのまま残ります。

- folderskin-communityの`index.json`には、すべてのパックが載っています。ID、名前、作者、ライセンス、タグ、スキンの数、そして中身（`pack.json`とすべての画像）を正確に表すハッシュです。アプリは追加したスキンと一緒にこのハッシュを保存し、それによってパックにアップデートがあることを知ります。各項目には、パックが最初に公開された日時（`"added"`。Unix秒で、最初のIDで`pack.json`を追加したコミットの時刻）と、`official.json`に載っているパックなら`"official": true`も入ります。パックの一覧と並んでいる`"moved"`は[moved.json](https://folderskin.app/ja/docs/packs/#movedjson)の内容です。`index.json`は`folderskin-tools packs index`が書き出し、同時に`previews/<id>.png`（パックの最初の4つのスキンをフォルダとして並べた画像）も作ります。どちらもfolderskin-communityの`main`上で生成されるので、手で編集しないでください。
- `packs catalog`が書き出す`v2/`は、同じパックを、アプリがコンピュータ上で検索できるカタログの形にしたものです。その`head.json`は、現在のカタログを示し、`featured`と`official`のパックを挙げ、`moved`を含み、`https://packs.folderskin.app`のように同じツリーを配信するミラーを列挙します（[ミラー](https://folderskin.app/ja/docs/packs/#ミラー)）。アプリは各ファイルをまずミラーから取得し、失敗したときはGitHubから取得します。どちらの場合も、すべてのファイルをハッシュで検証します。0.1.7以降は、`head.json`自体もまず`https://packs.folderskin.app`から読み込みます。
- アプリがパックの画像をダウンロードするのは、パックを追加したときだけです。4枚ずつダウンロードし、届いた枚数を表示します。すべての画像を上の制限に照らしてチェックし、ひとつでも通らなければ何も保存しません。すべて通ったらまとめて保存するので、パックが中途半端に追加されることはありません。
- `FOLDERSKIN_COMMUNITY_URL`を設定すると、アプリは別のfolderskin-communityのコピーを参照します。たとえば、チェックアウトのルートで`python3 -m http.server`を実行して配信し、この変数を`http://localhost:8000`にすれば、パックを最初から最後まで試せます。

`index.json`と`head.json`には時とともにフィールドが増えていきますが、どのバージョンのアプリも知っているフィールドだけを読み、残りは読み飛ばします。`pack.json`はその逆で、仕様にないフィールドを受け付けないため、今後も何かを追加することはできません。パックについての新しい情報は、インデックスのほうに入れます。

## 注目パックと公式パック

folderskin-communityのルートには、`packs/`と並んで2つのリストがあり、編集するのはメンテナだけです。どちらも`["classic-art-k7q2mx", "colours-a2b3c4"]`のような、パックIDのJSONリストです。

| ファイル | 役割 |
|---|---|
| `featured.json` | 初回起動時に提案され、コミュニティで最初に表示されるパック（この順番で表示） |
| `official.json` | コミュニティ、パックのビューア、Webサイトで**公式**の印が付くパック |

どちらも省略できます。IDはすべて`packs/`にあるパックでなければならず、2回載っているIDは1回として数えます。どちらかのリストに存在しないパックが載っていると、`packs index`と`packs catalog`は処理を止めて何も書き出しません。そのため、リネームや削除をしたパックが抜けたまま残ることはありません。プルリクエストのチェックでは`packs catalog`を実行するので、マージ前に見つかります。`packs rename`は両方のリストを自分で書き換えます。folderskin-communityのPacksワークフローは、`paths`に含まれるファイルが変わるとインデックスを作り直します。そのため、両方のリストは`packs/**`と並んで`paths`に含めておく必要があり、`moved.json`も同様です。

## インストールリンク

`folderskin://install?pack=<id>`を開くと、FolderSkinがコミュニティでパック`<id>`を表示して追加します。パックの**追加**ボタンとまったく同じ動作で、進行状況の表示も最後のメッセージも同じです。まずウィンドウが最前面に出ます。GitHubにもう一度問い合わせても、そのIDのパックがコミュニティにない場合は、FolderSkinがそのことを伝え、検索するよう提案します。すでにライブラリにあるパックは、開いたうえで追加済みであることを伝えます。0.1.7以降は、古いIDのリンクを開くと、移動先のパックが開きます（[moved.json](https://folderskin.app/ja/docs/packs/#movedjson)）。

FolderSkinがリンクを受け付けるのは、厳密に次の形のときだけです。スキームが`folderskin`で、`install`がホスト（`folderskin://install?…`）またはパス全体（`folderskin:install?…`）であること。ユーザ名、パスワード、ポートを含まないこと。`pack`がちょうど1つあり、それがパックID（小文字の英字と数字でできた単語を1つのハイフンでつないだ、最大40文字の文字列）であること。ほかのパラメータは読み飛ばし、それ以外の形のリンクは無視します。

スキームはインストーラが登録します。macOSではアプリの`Info.plist`、Windowsではインストーラ、Linuxでは`.deb`と`.rpm`のデスクトップエントリです。AppImageはインストールされるわけではないので、起動時に自分で登録します。WindowsとLinuxでは、リンクを開くと2つ目のFolderSkinが起動し、すでに動いているFolderSkinにリンクを渡して終了するので、同時に動くのは常に1つだけです。

試すには：

| | |
|---|---|
| macOS | アプリをビルドし（`pnpm tauri build --bundles app`）、`target/release/bundle/macos/FolderSkin.app`を一度開いて、macOSにスキームを登録します（`/Applications`にコピーしたものがいちばん確実です）。そのあと`open 'folderskin://install?pack=classic-art'`を実行します。macOSはバンドルされたアプリにしかリンクを送らないので、`pnpm tauri dev`がリンクを受け取ることはありません |
| Windows | ビルドをインストールするか、`pnpm tauri dev`を実行します（開発ビルドは自分でスキームを登録します）。そのあと、コマンドプロンプトで`start "" "folderskin://install?pack=classic-art"`を実行するか、「ファイル名を指定して実行」（Windows+R）に同じリンクを入力します |
| Linux | `.deb`か`.rpm`をインストールするか、AppImageを一度起動するか、`pnpm tauri dev`を実行します。そのあと`xdg-open 'folderskin://install?pack=classic-art'`を実行します |
| ブラウザでのプレビュー | `pnpm dev`を実行し、`http://localhost:14200/?install=classic-art`を開きます |

WindowsやLinuxで`pnpm tauri dev`を実行する前に、インストール済みのFolderSkinを終了してください。すでに動いているものがあると、新しく起動したほうはそちらにリンクを渡して終了してしまいます。スキームを登録した開発ビルドは、インストーラや別のビルドが登録し直すまで、その登録を持ち続けます。

## インストール数

コミュニティのパックが追加されると、FolderSkinはそのパックのIDをコミュニティサービスに伝えます。`POST https://community.folderskin.app/v1/packs/<id>/installs`を、本文なしで送るだけです。送るのはこれだけで、アカウントも、デバイスIDも、ライブラリやフォルダに関する情報も送りません（FolderSkinのほかのリクエストと同じく、User-Agentにはアプリのバージョンが入ります）。送信はパックを保存したあとに行い、5秒で打ち切ります。送信を待つ処理はなく、失敗しても報告されません。開発ビルドと、別のコピーからパックを読み込んでいるビルド（`FOLDERSKIN_COMMUNITY_URL`）は、`FOLDERSKIN_COMMUNITY_API`で送信先のサービスを指定しない限り何も送りません。

サービスは、ネットワークとパックの組み合わせごとに1日1回だけ追加を数え、対象は公開済みの`index.json`にあるパックだけです。古いIDでの追加は移動先のパックの分として数えるので、0.1.7より前のアプリからの追加も数えられます。サービスが保存するのは、パックごとの回数と、その日（UTC）の終わりまでの、リクエスト元ネットワークのソルト付きハッシュです。これにより、同じ日に同じパックをもう一度追加しても二重には数えません。これらのハッシュは毎日のクリーンアップで削除されます。アドレスは保存しません。

folderskin.appは、`GET https://community.folderskin.app/v1/packs/installs`から回数を読み込みます。レスポンスは`{"version": 1, "installs": {"classic-art-k7q2mx": 42}}`の形で、5分間キャッシュされます。詳しくは[services/community/README.md](https://github.com/prajwal-svm/folderskin/blob/main/services/community/README.md)をご覧ください。
