diff --git a/.claude/skills/grow-quicksheet/drafts/extensions/quicksheet-case-ext/.gitignore b/.claude/skills/grow-quicksheet/drafts/extensions/quicksheet-case-ext/.gitignore
new file mode 100644
index 00000000..ac8d21bd
--- /dev/null
+++ b/.claude/skills/grow-quicksheet/drafts/extensions/quicksheet-case-ext/.gitignore
@@ -0,0 +1,3 @@
+bin/
+obj/
+*.user
diff --git a/.claude/skills/grow-quicksheet/drafts/extensions/quicksheet-case-ext/CaseExtension.csproj b/.claude/skills/grow-quicksheet/drafts/extensions/quicksheet-case-ext/CaseExtension.csproj
new file mode 100644
index 00000000..4f69373d
--- /dev/null
+++ b/.claude/skills/grow-quicksheet/drafts/extensions/quicksheet-case-ext/CaseExtension.csproj
@@ -0,0 +1,10 @@
+
+
+ Exe
+ net9.0
+ enable
+ enable
+ QuickSheetCase
+ CaseExtension
+
+
diff --git a/.claude/skills/grow-quicksheet/drafts/extensions/quicksheet-case-ext/LICENSE b/.claude/skills/grow-quicksheet/drafts/extensions/quicksheet-case-ext/LICENSE
new file mode 100644
index 00000000..dbaa22be
--- /dev/null
+++ b/.claude/skills/grow-quicksheet/drafts/extensions/quicksheet-case-ext/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2026 cemheren
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/.claude/skills/grow-quicksheet/drafts/extensions/quicksheet-case-ext/Program.cs b/.claude/skills/grow-quicksheet/drafts/extensions/quicksheet-case-ext/Program.cs
new file mode 100644
index 00000000..6b7f6fb8
--- /dev/null
+++ b/.claude/skills/grow-quicksheet/drafts/extensions/quicksheet-case-ext/Program.cs
@@ -0,0 +1,166 @@
+using System.Net.Http.Headers;
+using System.Text.Json;
+
+// QuickSheet case extension — US case law lookup via CourtListener's free API.
+//
+// Cell usage:
+// case: Roe v Wade
+// case: 410 U.S. 113
+// case: "unreasonable search" fourth amendment
+//
+// Output: up to gridRows results. Columns: case name, citation, court, year, snippet.
+// API: https://www.courtlistener.com/api/rest/v4/search/?type=o&q=
+// No auth required for basic search (rate-limited to ~100 req/hr unauthenticated).
+
+var http = new HttpClient();
+http.DefaultRequestHeaders.UserAgent.Add(new ProductInfoHeaderValue("QuickSheetCaseExt", "1.0"));
+http.Timeout = TimeSpan.FromSeconds(10);
+
+Console.OutputEncoding = System.Text.Encoding.UTF8;
+Console.WriteLine(JsonSerializer.Serialize(new
+{
+ type = "register",
+ prefix = "case",
+ name = "Case Law",
+ version = "1.0.0",
+}));
+Console.Out.Flush();
+
+string? line;
+while ((line = Console.ReadLine()) != null)
+{
+ if (string.IsNullOrWhiteSpace(line)) continue;
+ try
+ {
+ using var doc = JsonDocument.Parse(line);
+ if (!doc.RootElement.TryGetProperty("type", out var t)) continue;
+ if (t.GetString() != "activate") continue;
+ HandleActivate(doc.RootElement);
+ }
+ catch { /* ignore malformed input */ }
+}
+
+void HandleActivate(JsonElement root)
+{
+ string id = root.TryGetProperty("id", out var idEl) ? idEl.GetString() ?? "" : "";
+ int gridRows = root.TryGetProperty("gridRows", out var gr) ? gr.GetInt32() : 5;
+
+ string query = "";
+ if (root.TryGetProperty("params", out var p) && p.ValueKind == JsonValueKind.Array)
+ {
+ var en = p.EnumerateArray();
+ if (en.MoveNext()) query = en.Current.GetString() ?? "";
+ }
+ query = query.Trim();
+
+ if (string.IsNullOrEmpty(query))
+ {
+ Emit(id, new[] { new Cell(0, 0, "case: ") });
+ return;
+ }
+
+ var results = Search(query, gridRows);
+ if (results.Count == 0)
+ {
+ Emit(id, new[] { new Cell(0, 0, "No results"), new Cell(0, 1, query) });
+ return;
+ }
+
+ var cells = new List();
+ for (int r = 0; r < results.Count && r < gridRows; r++)
+ {
+ var c = results[r];
+ cells.Add(new Cell(r, 0, c.caseName));
+ cells.Add(new Cell(r, 1, c.citation));
+ cells.Add(new Cell(r, 2, c.court));
+ cells.Add(new Cell(r, 3, c.year));
+ cells.Add(new Cell(r, 4, c.snippet));
+ }
+ Emit(id, cells);
+}
+
+List Search(string query, int limit)
+{
+ var results = new List();
+ try
+ {
+ string encoded = Uri.EscapeDataString(query);
+ string url = $"https://www.courtlistener.com/api/rest/v4/search/?type=o&q={encoded}&page_size={Math.Min(limit, 20)}";
+ var response = http.GetAsync(url).GetAwaiter().GetResult();
+ if (!response.IsSuccessStatusCode) return results;
+
+ string json = response.Content.ReadAsStringAsync().GetAwaiter().GetResult();
+ using var doc = JsonDocument.Parse(json);
+
+ if (!doc.RootElement.TryGetProperty("results", out var arr)) return results;
+
+ foreach (var item in arr.EnumerateArray())
+ {
+ string caseName = item.TryGetProperty("caseName", out var cn)
+ ? cn.GetString() ?? "" : "";
+ string citation = item.TryGetProperty("citation", out var cit)
+ ? FirstCitation(cit) : "";
+ string court = item.TryGetProperty("court_citation_string", out var ct)
+ ? ct.GetString() ?? "" : "";
+ string year = item.TryGetProperty("dateFiled", out var df)
+ ? ExtractYear(df.GetString()) : "";
+ string snippet = item.TryGetProperty("snippet", out var sn)
+ ? StripHtml(sn.GetString() ?? "") : "";
+
+ if (caseName.Length == 0 && citation.Length == 0) continue;
+ results.Add(new CaseResult(
+ Truncate(caseName, 40),
+ Truncate(citation, 25),
+ Truncate(court, 15),
+ year,
+ Truncate(snippet, 60)
+ ));
+ }
+ }
+ catch { /* network error — return empty */ }
+ return results;
+}
+
+static string FirstCitation(JsonElement el)
+{
+ if (el.ValueKind == JsonValueKind.Array)
+ {
+ foreach (var c in el.EnumerateArray())
+ {
+ string? s = c.GetString();
+ if (!string.IsNullOrEmpty(s)) return s;
+ }
+ }
+ if (el.ValueKind == JsonValueKind.String) return el.GetString() ?? "";
+ return "";
+}
+
+static string ExtractYear(string? date)
+{
+ if (date == null || date.Length < 4) return "";
+ return date[..4];
+}
+
+static string StripHtml(string s)
+{
+ // Remove , , , , and other common HTML tags from snippets
+ var clean = System.Text.RegularExpressions.Regex.Replace(s, "<[^>]+>", "");
+ clean = clean.Replace("&", "&").Replace("<", "<").Replace(">", ">");
+ clean = clean.Replace(""", "\"").Replace("'", "'");
+ clean = clean.Replace("\n", " ").Replace("\r", "");
+ // Collapse whitespace
+ clean = System.Text.RegularExpressions.Regex.Replace(clean, @"\s+", " ").Trim();
+ return clean;
+}
+
+static string Truncate(string s, int max) =>
+ s.Length <= max ? s : s[..(max - 1)] + "…";
+
+void Emit(string id, IEnumerable| cells)
+{
+ Console.WriteLine(JsonSerializer.Serialize(new { type = "write", id, cells }));
+ Console.Out.Flush();
+}
+
+record Cell(int r, int c, string v);
+record CaseResult(string caseName, string citation, string court, string year, string snippet);
diff --git a/.claude/skills/grow-quicksheet/drafts/extensions/quicksheet-case-ext/README.md b/.claude/skills/grow-quicksheet/drafts/extensions/quicksheet-case-ext/README.md
new file mode 100644
index 00000000..0d2da93d
--- /dev/null
+++ b/.claude/skills/grow-quicksheet/drafts/extensions/quicksheet-case-ext/README.md
@@ -0,0 +1,60 @@
+# quicksheet-case-ext
+
+A QuickSheet extension that looks up US case law citations and keywords via
+[CourtListener](https://www.courtlistener.com/)'s free public API.
+
+```
+ext: github:cemheren/quicksheet-case-ext
+case: Roe v Wade
+case: 410 U.S. 113
+case: "unreasonable search" fourth amendment
+```
+
+## What you get
+
+Up to 5 results per query (configurable via grid rows). Each row shows:
+
+| Case Name | Citation | Court | Year | Snippet |
+|------------------------------|----------------|-------|------|--------------------------------------------------|
+| Roe v. Wade | 410 U.S. 113 | SCOTUS| 1973 | right of privacy...broad enough to encompass... |
+| Planned Parenthood v. Casey | 505 U.S. 833 | SCOTUS| 1992 | undue burden standard...liberty of the woman... |
+
+## Features
+
+- **Citation search**: type a standard reporter citation (`410 U.S. 113`, `347 U.S. 483`) for a direct hit.
+- **Keyword search**: natural language queries across millions of opinions.
+- **No API key required**: CourtListener's search endpoint is free and open (rate-limited to ~100 req/hr).
+- **HTML-stripped snippets**: result excerpts are cleaned of markup for readable cell display.
+
+## Use cases
+
+- Law students reviewing cases for class prep — keep citations on your desktop.
+- Paralegals quickly verifying citation accuracy without leaving their workspace.
+- Anyone who wants an ambient "legal research sticky note" on their wallpaper.
+
+## Build
+
+```bash
+dotnet build CaseExtension.csproj
+```
+
+.NET 9, MIT, zero NuGet dependencies.
+
+## Protocol
+
+Standard QuickSheet JSON-lines protocol over stdin/stdout:
+
+1. On startup, emit `{"type":"register","prefix":"case","name":"Case Law","version":"1.0.0"}`.
+2. On each `{"type":"activate","id":"...","params":[""],"gridRows":N}`, search CourtListener and reply with `{"type":"write","id":"...","cells":[...]}`.
+
+See [docs/extension-protocol.md](https://github.com/cemheren/QuickSheet/blob/main/docs/extension-protocol.md) in the main repo.
+
+## API
+
+Uses CourtListener REST API v4: `https://www.courtlistener.com/api/rest/v4/search/?type=o&q=`
+
+CourtListener is operated by [Free Law Project](https://free.law/), a 501(c)(3) non-profit. The API is free for reasonable use. If you plan heavy usage, consider [registering for a token](https://www.courtlistener.com/sign-in/).
+
+## License
+
+MIT.
diff --git a/.claude/skills/grow-quicksheet/drafts/extensions/quicksheet-case-ext/quicksheet-extension.json b/.claude/skills/grow-quicksheet/drafts/extensions/quicksheet-case-ext/quicksheet-extension.json
new file mode 100644
index 00000000..2bdbeb8f
--- /dev/null
+++ b/.claude/skills/grow-quicksheet/drafts/extensions/quicksheet-case-ext/quicksheet-extension.json
@@ -0,0 +1,8 @@
+{
+ "name": "case",
+ "version": "1.0.0",
+ "prefix": "case",
+ "description": "US case law lookup — search citations or keywords via CourtListener's free API",
+ "entry": "dotnet run --project CaseExtension.csproj",
+ "minProtocolVersion": 1
+}
diff --git a/.claude/skills/grow-quicksheet/log.md b/.claude/skills/grow-quicksheet/log.md
index 7b845528..6858f100 100644
--- a/.claude/skills/grow-quicksheet/log.md
+++ b/.claude/skills/grow-quicksheet/log.md
@@ -1,3 +1,27 @@
+## 2026-06-16 (docs: accountant landing page)
+
+- Stars: 0 (Δ 0).
+- Action: Created `docs/for-accountants.md` landing page + `examples/accountant-dashboard.csv` starter sheet. Targets freelance accountants/bookkeepers with 1099, mileage, depreciation, margin, salestax, payroll extensions. Added README cross-link.
+- Bucket: A (audience landing page).
+- Outcome: PR #431.
+- Follow-up: None queued — all backlog landing pages now have PRs (homelab merged, traders/sre/students on main, dms/writers/artists/accountants as open PRs).
+
+## 2026-06-16 (feat: stdin pipe support)
+
+- Stars: 0 (Δ 0).
+- Action: Implemented `-` as file argument to read CSV from stdin in headless export modes. Reads stdin to temp file, processes, cleans up. Added error for non-headless use. Updated --help.
+- Bucket: E (issue fix — #320).
+- Outcome: PR #396 updated (commit 48f4104). Closes #320.
+- Follow-up: #318 (NO_COLOR) or #158 (usage pattern/tabs) are next open issues.
+
+## 2026-06-16 (feat: shell completions for bash/zsh/fish)
+
+- Stars: 0 (Δ 0).
+- Action: Rewrote shell completion scripts (bash, zsh, fish) on existing PR #393 branch. Covers all CLI flags + .csv file completion. Added docs/shell-completions.md with install instructions.
+- Bucket: E (issue fix — #372).
+- Outcome: PR #393 updated (force-pushed commit d92d1d5). Closes #372.
+- Follow-up: #319 (--delimiter flag) or #320 (stdin pipe) are next smallest qualifying issues with existing PRs. No new unaddressed issues remain.
+
## 2026-05-19 (no-op #31 — #139 still open, hold)
- Stars: 0 (Δ 0). PR #139 still open, no other action items. Hold.
diff --git a/README.md b/README.md
index 29fa4076..574a7171 100644
--- a/README.md
+++ b/README.md
@@ -231,6 +231,7 @@ tls: github.com, 1, 4
| `caniuse:` | Browser compat lookup via caniuse-db — `caniuse: css-grid` · `caniuse: flexbox` · `caniuse: webassembly`. Shows support across Chrome/Firefox/Safari/Edge + usage %. No API key. | `ext: github:Deskworks/quicksheet-caniuse` |
| `gem:` | Ruby gem lookup via rubygems.org — version, total downloads, authors, license, homepage, description. `gem: rails` (detail) · `gem: search json parser` (top 5). No API key. | `ext: github:Deskworks/quicksheet-rubygems` |
| `hackage:` | Haskell package lookup from Hackage — `hackage: aeson` (version, synopsis, author, category, license, homepage) · `hackage: search json` (top results). No API key. | `ext: github:Deskworks/quicksheet-hackage` |
+| `case:` | US case law lookup — search by citation or keywords via CourtListener. `case: Roe v Wade` · `case: 410 U.S. 113` · `case: "fourth amendment" search`. No API key. | `ext: github:cemheren/quicksheet-case-ext` |
See [docs/extensions.md](docs/extensions.md) for the full directory with descriptions and protocol docs.
diff --git a/docs/tour.md b/docs/tour.md
index d680219b..1f6d8bb9 100644
--- a/docs/tour.md
+++ b/docs/tour.md
@@ -84,6 +84,7 @@ Currently public:
- [`quicksheet-qtr`](https://github.com/Deskworks/quicksheet-qtr) — IRS quarterly tax deadline countdown.
- [`quicksheet-fx`](https://github.com/Deskworks/quicksheet-fx) — live currency conversion (200+ currencies, ECB rates).
- [`quicksheet-rate`](https://github.com/Deskworks/quicksheet-rate) — freelance hourly rate calculator (taxes, benefits, billable time).
+- [`quicksheet-case-ext`](https://github.com/cemheren/quicksheet-case-ext) — US case law lookup via CourtListener (citation or keyword search).
[Write your own](../README.md#build-your-own) in whatever language you want — the protocol is two message types.
| |