@@ -1,11 +1,11 @@
1−# Grove
1+# Diagonal
22
33Dia-style tab groups for Brave on macOS, named by Apple's on-device model through the `fm` command-line tool over Chromium native messaging. No HTTP server, no cloud call, no model download.
44
55What it does (v1):
66
771. **Opener grouping.** Cmd-click a link from an ungrouped, unpinned tab and both tabs become a group, titled with the site's hostname until the model names it. An opener group that shrinks to one tab dissolves.
8−2. **Naming loop.** Groups Grove made get an `emoji + 2–4 word` title from the model. The title is recomputed after 4 s of quiet when membership changes, stays put when the model only paraphrases, and is never touched again once you type a title yourself.
8+2. **Naming loop.** Groups Diagonal made get an `emoji + 2–4 word` title from the model. The title is recomputed after 4 s of quiet when membership changes, stays put when the model only paraphrases, and is never touched again once you type a title yourself.
993. **Organize this window** (toolbar or Alt+Shift+G). Clusters ungrouped tabs into topic groups in batches of 18. Undo is available for an hour.
10104. **Tidy sweep.** Tabs idle for 24 h move to a collapsed grey "Parked" group (Ask mode by default). After another 48 h they are archived and closed, with restore from the popup.
1111
@@ -21,7 +21,7 @@ The model sees a tab's title, address and `<meta name="description">`, and nothi
2121
2222### 1. The extension
2323
24−The built extension is in `dist/` (or `grove-extension/` next to this folder in the delivery).
24+The built extension is in `dist/` (or `diagonal-extension/` next to this folder in the delivery).
2525
26261. Open `brave://extensions` and turn on **Developer mode**.
27272. Click **Load unpacked** and pick the `dist/` folder.
@@ -37,25 +37,25 @@ Without Nix:
3737scripts/install-manifest.sh # add --beta / --nightly / --all-channels for other Brave channels
3838```
3939
40−This copies `host/` to `~/.local/share/grove-host`, links `~/.local/bin/grove-host`, pins its shebang to your `python3`, writes `~/Library/Application Support/BraveSoftware/Brave-Browser/NativeMessagingHosts/io.grove.host.json`, runs `grove-host --install-schemas`, then `grove-host --selftest`.
40+This copies `host/` to `~/.local/share/diagonal-host`, links `~/.local/bin/diagonal-host`, pins its shebang to your `python3`, writes `~/Library/Application Support/BraveSoftware/Brave-Browser/NativeMessagingHosts/io.diagonal.host.json`, runs `diagonal-host --install-schemas`, then `diagonal-host --selftest`.
4141
4242With Nix (nix-darwin + home-manager):
4343
4444```nix
4545# flake inputs
46−grove.url = "path:/path/to/grove"; # or a git URL once it lives in a repo
46+diagonal.url = "path:/path/to/diagonal"; # or a git URL once it lives in a repo
4747
4848# home-manager config
49−imports = [ inputs.grove.homeManagerModules.default ];
50−programs.grove.enable = true;
51−# programs.grove.channels = [ "Brave-Browser" "Brave-Browser-Beta" ];
49+imports = [ inputs.diagonal.homeManagerModules.default ];
50+programs.diagonal.enable = true;
51+# programs.diagonal.channels = [ "Brave-Browser" "Brave-Browser-Beta" ];
5252```
5353
54−`nix build .#extension` builds `dist/` into `./result`; `nix build .#grove-host` builds the host.
54+`nix build .#extension` builds `dist/` into `./result`; `nix build .#diagonal-host` builds the host.
5555
5656### 3. Check it
5757
58−Open Grove's settings page and click **Run self-test**. Expect `fmAvailable: true`, `schemasOk: true` and a title for the three fixture tabs. From a terminal, `grove-host --selftest` runs the same checks.
58+Open Diagonal's settings page and click **Run self-test**. Expect `fmAvailable: true`, `schemasOk: true` and a title for the three fixture tabs. From a terminal, `diagonal-host --selftest` runs the same checks.
5959
6060## Keyboard shortcuts
6161
@@ -81,20 +81,20 @@ npm run smoke # Linux: loads dist/ in Chromium with the real host and a
8181npm run check # typecheck + both unit suites
8282```
8383
84−Layout follows the spec: `src/background/` (engine, naming, organize, tidy, host client, state, settings), `src/content/meta.ts`, `src/popup/`, `src/options/`, `src/shared/`, and `host/` (`grove-host.py`, `prompts.py`, `validate.py`, `emoji.txt`, `tests/`).
84+Layout follows the spec: `src/background/` (engine, naming, organize, tidy, host client, state, settings), `src/content/meta.ts`, `src/popup/`, `src/options/`, `src/shared/`, and `host/` (`diagonal-host.py`, `prompts.py`, `validate.py`, `emoji.txt`, `tests/`).
8585
8686`host/emoji.txt` is the single emoji list. `npm run gen-emoji` regenerates `src/shared/emoji.gen.ts` from it.
8787
8888### Pinning a different extension ID
8989
90−`scripts/gen-key.sh` creates `key.pem` if it is missing, writes the public key into `manifest.json`, the ID into `extension-id` and into `ALLOWED_ORIGIN` in `host/grove-host.py`, and prints the ID. The shipped ID comes from a key that is not included; you only need the private key to pack a `.crx`. Running the script without a `key.pem` makes a new key and a new ID, so after that rebuild and rerun the host installer.
90+`scripts/gen-key.sh` creates `key.pem` if it is missing, writes the public key into `manifest.json`, the ID into `extension-id` and into `ALLOWED_ORIGIN` in `host/diagonal-host.py`, and prints the ID. The shipped ID comes from a key that is not included; you only need the private key to pack a `.crx`. Running the script without a `key.pem` makes a new key and a new ID, so after that rebuild and rerun the host installer.
9191
9292## Where this differs from the spec, and why
9393
9494- **New tab in group is Alt+Shift+T, not Alt+Cmd+T.** Chromium refuses to load an extension whose manifest suggests Alt+Command+T (it is treated as Ctrl+Alt, which is reserved for AltGr). You can still bind Alt+Cmd+T by hand at `brave://extensions/shortcuts` if Brave accepts it there.
95−- **Query strings.** The spec keeps the query only when the path is empty, but also wants `youtube.com/watch?v=…` kept. Grove keeps the whole query when the path is empty and otherwise keeps only content keys (`v`, `q`, `query`, `search_query`, `search`, `s`, `k`, `id`, `p`, `list`, `page`), dropping `utm_*` and similar.
95+- **Query strings.** The spec keeps the query only when the path is empty, but also wants `youtube.com/watch?v=…` kept. Diagonal keeps the whole query when the path is empty and otherwise keeps only content keys (`v`, `q`, `query`, `search_query`, `search`, `s`, `k`, `id`, `p`, `list`, `page`), dropping `utm_*` and similar.
9696- **Grey is reserved for the Parked group.** Site colours hash over the other eight colours.
97−- **Organize only adds tabs to Grove's own groups,** not to groups you made by hand, unless "Name my own groups" is on.
97+- **Organize only adds tabs to Diagonal's own groups,** not to groups you made by hand, unless "Name my own groups" is on.
9898- **An Organize group whose title fails validation is still created,** with the hostname as a placeholder, and the naming loop names it.
9999- **Restarts.** Tab and group IDs change when Brave restarts, so records are matched to live groups by title and colour, which keeps managed groups managed.
100100
@@ -102,7 +102,7 @@ Layout follows the spec: `src/background/` (engine, naming, organize, tidy, host
102102
103103Everything was tested against a fake `fm`. These are open until someone runs it on macOS 27 (section 16 of the spec):
104104
105−- The `fm schema` flags. `grove-host --install-schemas` tries the nested organize schema and falls back to the two flat schemas plus two calls if that fails. The flags are in `SCHEMA_COMMANDS` at the top of `host/grove-host.py`.
105+- The `fm schema` flags. `diagonal-host --install-schemas` tries the nested organize schema and falls back to the two flat schemas plus two calls if that fails. The flags are in `SCHEMA_COMMANDS` at the top of `host/diagonal-host.py`.
106106- `fm` stderr wording. `classify()` matches keywords, so an unexpected message shows as `FM_ERROR` with the raw text on the settings page.
107107- Whether `fm respond` can read the prompt from stdin. Until then, the prompt is an argument and is briefly visible in `ps`.
108108- The context window. `CHAR_BUDGET` is 10,000 characters, assuming 4,096 tokens. Double it (and `NAME_ITEM_CAP` / `ORGANIZE_ITEM_CAP`) once 8,192 is confirmed.