From 6f0bde405f30ac24317ba9ef7edd38660acdbea8 Mon Sep 17 00:00:00 2001 From: barkz Date: Fri, 11 Sep 2026 21:39:58 -0500 Subject: [PATCH] fix: stop the command listings overflowing their card MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit "What you can do" and the Glean Personal listing were 96-character space-aligned code blocks. A
 scrolls rather than wraps, so below
roughly 830px of content width the commands ran past the edge of the
card and had to be dragged sideways to read — which is what they did
on any window narrower than about 850px.

Both listings are command/effect pairs, so they are tables, not code
blocks. As tables they wrap, they never scroll, and the two ugly
continuation rows (a line of spaces and an arrow) are gone. GitHub
gains the same fix — those blocks scrolled sideways on a phone there
too.

The builder tags a table whose every row leads with a code span as a
command table, so the stylesheet can give the command column half the
width and style it as code rather than bold prose. Two line-breaking
details in that column: hyphens are Unicode break opportunities, so
"--object-type" split after the dashes until word-break: keep-all went
on, with overflow-wrap as the fallback for a token wider than the
column.

The quickstart block kept its terminal framing but lost its widest
line: the "or use a token" aside moved into prose below it. The widest
code line on the page is now 70 characters, down from 96, and a test
fails if any block goes past 80 again.

Tests: 1,090 -> 1,094.

Co-Authored-By: Claude Opus 5 (1M context) 
---
 .github/pages/build.py      | 12 +++++++++-
 .github/pages/template.html | 12 ++++++++++
 CLAUDE.md                   |  2 +-
 README.md                   | 44 ++++++++++++++++++-------------------
 docs/TESTING.md             |  4 ++--
 tests/test_pages_build.py   | 31 ++++++++++++++++++++++++++
 6 files changed, 78 insertions(+), 27 deletions(-)

diff --git a/.github/pages/build.py b/.github/pages/build.py
index d94aa71..3663434 100644
--- a/.github/pages/build.py
+++ b/.github/pages/build.py
@@ -117,6 +117,16 @@ def is_table_divider(cells):
     return bool(cells) and all(re.fullmatch(r":?-{3,}:?", cell or "") for cell in cells)
 
 
+def is_command_table(body):
+    """True when every row leads with a code span -- a command/effect listing."""
+    if not body:
+        return False
+    return all(
+        len(row) >= 2 and row[0].startswith("`") and row[0].endswith("`") and len(row[0]) > 2
+        for row in body
+    )
+
+
 def render_table(rows):
     """GFM table. An all-empty header row is dropped -- the README uses those
     purely to get a two-column layout, and a blank  is just a gap."""
@@ -125,7 +135,7 @@ def render_table(rows):
         head, body = rows[0], rows[2:]
         if not any(cell for cell in head):
             head = None
-    parts = ['']
+    parts = ['
' if is_command_table(body) else "
"] if head: parts.append("%s" % "".join( "" % inline(cell) for cell in head)) diff --git a/.github/pages/template.html b/.github/pages/template.html index ddafd33..6db266f 100644 --- a/.github/pages/template.html +++ b/.github/pages/template.html @@ -203,6 +203,18 @@ .doc td + td { color: var(--ink-2); } .doc td p { color: var(--ink-2); } .doc td :last-child { margin-bottom: 0; } + /* command listings: the command needs room, and must wrap rather than scroll */ + .doc table.cmd td:first-child { width: 50%; font-weight: 400; } + .doc table.cmd td:first-child code { + background: none; padding: 0; font-size: .8em; + color: var(--accent-ink); + /* break between words only: hyphens are line-break opportunities in + Unicode, which would split "--object-type" after the dashes. + break-word stays as the fallback for a token wider than the column. */ + word-break: keep-all; overflow-wrap: break-word; + } + .doc table.cmd td + td { color: var(--ink-2); } + /* the README's two-column feature table is layout, not data */ .doc td[width="50%"] { width: 50%; font-weight: 400; padding: 20px 22px; } .doc td[width="50%"] p { color: var(--ink-2); max-width: 46ch; } diff --git a/CLAUDE.md b/CLAUDE.md index 8512417..c0a1f40 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -18,7 +18,7 @@ python3 install.py # --cli-only, --dev, --prefix, --verify, --uninstal # Pipe a single command (non-interactive; cli.py detects a non-tty stdin) echo '/search "q2 plan"' | python3 -m glean_code -# Run the full test suite (1,090 tests, stdlib unittest — works with or without pytest) +# Run the full test suite (1,094 tests, stdlib unittest — works with or without pytest) python3 -m pytest tests/ python3 -m unittest discover tests/ diff --git a/README.md b/README.md index 03b3804..ca3d5c5 100644 --- a/README.md +++ b/README.md @@ -44,12 +44,12 @@ python3 install.py && glean # installed, with a macOS Spotlight app ``` ```text -/login acme # browser SSO — or /login --token +/login acme /search "quarterly planning" /chat "summarise the Q2 plan" ``` -No login? You're still up and running — mock mode serves ranked results from a real corpus. +`/login acme` opens browser SSO; `/login --token ` takes a Glean-issued token instead. No login at all? You're still up and running — mock mode serves ranked results from a real corpus. **→ [Install guide](docs/INSTALL.md)** · **[Configuration](docs/CONFIGURATION.md)** · **[Command index](docs/COMMAND_INDEX.md)** @@ -57,20 +57,18 @@ No login? You're still up and running — mock mode serves ranked results from a ## What you can do -```text -?login into acme and search for "Q2 plan" ← natural language, planned + confirmed -/search "checkout incident" --datasource jira ← search every connected source -/chat "what changed in the pricing doc?" ← threaded Glean Assistant chat -/agents.run "draft the release notes" ← run agents, call tools -/insights --all --export insights.csv ← usage metrics straight to CSV -/debug.user gdrive alice@example.com ← why can't Alice see that doc? -/index.bulk-documents --path ./docs/ --datasource custom1 --object-type Article --dry-run - ← index a folder, inspect the payload first -/metadata.attach --doc-id --group tickets --values owner=alice - ← enrich docs without re-uploading -/personal index ~/Documents --label docs ← index your own files, locally -/flow show ← draw the investigations you ran -``` +| Command | What it does | +| --- | --- | +| `?login into acme and search for "Q2 plan"` | Natural language, planned and confirmed before anything runs | +| `/search "checkout incident" --datasource jira` | Search every connected source | +| `/chat "what changed in the pricing doc?"` | Threaded Glean Assistant chat | +| `/agents.run "draft the release notes"` | Run agents, call tools | +| `/insights --all --export insights.csv` | Usage metrics straight to CSV | +| `/debug.user gdrive alice@example.com` | Why can't Alice see that doc? | +| `/index.bulk-documents --path ./docs/ --datasource custom1 --object-type Article --dry-run` | Index a folder, inspecting the payload first | +| `/metadata.attach --doc-id --group tickets --values owner=alice` | Enrich docs without re-uploading them | +| `/personal index ~/Documents --label docs` | Index your own files, locally | +| `/flow show` | Draw the investigations you ran |
%s
", out) self.assertIn("--dev", out) + def test_command_table_is_tagged(self): + out = build.render( + "| Command | What it does |\n| --- | --- |\n" + "| `/search \"x\"` | Search |\n| `/flow show` | Draw |") + self.assertIn('
@@ -138,12 +136,12 @@ Sessions run down a rail in the order you worked; each connection branches off i Mock mode proves every command works offline against a fictional corpus. `/personal` points the same machinery at content that is actually yours. -```text -/personal index ~/Documents --label docs -/personal search "salary bands" --explain # which terms hit, which missed, and the bm25 score -/personal link && /personal related roadmap # a phrase graph, with shared phrases as evidence -/mode local # /search and /chat now answer from your files -``` +| Command | What it does | +| --- | --- | +| `/personal index ~/Documents --label docs` | Build the index from a folder | +| `/personal search "salary bands" --explain` | Which terms hit, which missed, and the bm25 score | +| `/personal link && /personal related roadmap` | A phrase graph, with shared phrases as evidence | +| `/mode local` | `/search` and `/chat` now answer from your files | SQLite FTS5, incremental on a content hash, `.docx`/`.xlsx`/`.pptx` read straight out of their ZIP-XML with the stdlib. **No server, no daemon, no Docker, no network, no credentials** — the whole index is one file you can copy between machines. Answers are labelled `[LOCAL INDEX]` and quote your passages verbatim; the REPL has no model in-process and will not invent prose. Four MCP tools expose the same index to an agent that does. @@ -178,7 +176,7 @@ The full Glean Code REPL — slash commands, status bar, mock/live switching, se | 🔐 **[SSO / OAuth](docs/SSO_OAUTH.md)** · **[Secure tokens](docs/SECURE_TOKENS.md)** | Browser sign-in, secure refs, the masking matrix | | 🔌 **[MCP server](docs/MCP.md)** | Glean as native tools in Claude Code, Claude Desktop, Cursor | | 🏛️ **[Architecture](docs/ARCHITECTURE.md)** · **[REST paths](docs/REST_PATHS.md)** | Module map, request flow, endpoints, how to add a command | -| ✅ **[Testing](docs/TESTING.md)** | Running the 1,090-test suite and what it covers | +| ✅ **[Testing](docs/TESTING.md)** | Running the 1,094-test suite and what it covers | | 🛟 **[Support](SUPPORT.md)** · **[Changelog](CHANGELOG.md)** | How to report a bug · release history | > [!NOTE] diff --git a/docs/TESTING.md b/docs/TESTING.md index a504661..c49675b 100644 --- a/docs/TESTING.md +++ b/docs/TESTING.md @@ -21,13 +21,13 @@ files, and they outrank the `Glean Code.app` launcher in `Cmd+Space`: export PYTHONPYCACHEPREFIX="$HOME/.cache/python" ``` -1,090 tests covering the client and every mock response, commands and dispatch, config, UI, auth, completion, help docs, the mock corpus, indexing-walk, scaffold, the installer, the MCP server, the flow mapper, the Pages site builder, and Glean Personal (text extraction, the index, the content graph, ranking explanations, local mode, and the local MCP tools). +1,094 tests covering the client and every mock response, commands and dispatch, config, UI, auth, completion, help docs, the mock corpus, indexing-walk, scaffold, the installer, the MCP server, the flow mapper, the Pages site builder, and Glean Personal (text extraction, the index, the content graph, ranking explanations, local mode, and the local MCP tools). ## Development notes Notes on the test suite added during development of glean-code-cli. -All 1,090 tests pass. Here's what was added across the development passes: +All 1,094 tests pass. Here's what was added across the development passes: `tests/test_commands_extended.py` (155 new tests) — covers all previously untested commands: diff --git a/tests/test_pages_build.py b/tests/test_pages_build.py index f9d7886..7f73841 100644 --- a/tests/test_pages_build.py +++ b/tests/test_pages_build.py @@ -102,6 +102,22 @@ def test_table_with_a_real_header_keeps_it(self): self.assertIn("
FlagEffect
', out) + + def test_a_prose_table_is_not_a_command_table(self): + out = build.render("| | |\n| --- | --- |\n| ⚡ **Fast** | it is quick |") + self.assertIn("
", out) + self.assertNotIn('class="cmd"', out) + + def test_command_table_needs_every_row_to_lead_with_code(self): + body = [["`/search`", "Search"], ["plain text", "Nope"]] + self.assertFalse(build.is_command_table(body)) + self.assertTrue(build.is_command_table([["`/a`", "x"], ["`/b`", "y"]])) + def test_divider_detection(self): self.assertTrue(build.is_table_divider(["---", ":---:"])) self.assertFalse(build.is_table_divider(["Flag", "Effect"])) @@ -222,6 +238,21 @@ def test_build_replaces_a_previous_output_dir(self): build.build(out_dir=out_dir) self.assertFalse(stale.exists()) + def test_no_code_block_is_wide_enough_to_overflow(self): + """Long space-aligned listings used to force a horizontal scroll inside + the card. Command listings belong in tables, which wrap; code blocks + stay narrow enough to fit the content column.""" + import html as html_mod + import re as re_mod + + with tempfile.TemporaryDirectory() as tmp: + page = (build.build(out_dir=pathlib.Path(tmp) / "_site") / "index.html").read_text() + widest = 0 + for block in re_mod.findall(r"
]*>(.*?)
", page, re_mod.S): + for line in html_mod.unescape(block).split("\n"): + widest = max(widest, len(line)) + self.assertLessEqual(widest, 80, "a code block is %d chars wide" % widest) + def test_no_unresolved_repo_relative_links_remain(self): with tempfile.TemporaryDirectory() as tmp: page = (build.build(out_dir=pathlib.Path(tmp) / "_site") / "index.html").read_text()