Skip to content

Start notebooks with import gemdb, and fix three things the CLI got wrong - #16

Merged
jgfoster merged 1 commit into
fix/restage-cli-when-stalefrom
feat/gemdb-samples-path-and-encoding
Aug 27, 2026
Merged

Start notebooks with import gemdb, and fix three things the CLI got wrong#16
jgfoster merged 1 commit into
fix/restage-cli-when-stalefrom
feat/gemdb-samples-path-and-encoding

Conversation

@jgfoster

Copy link
Copy Markdown
Member

Stacked on #15 — the cli.ts changes here sit on top of its restaging work. Merge #15 first, deleting its branch, and GitHub retargets this to main automatically (it only auto-retargets when the base is deleted).

Four things a user ran into in one sitting.

The sample code says import gemdb

A new notebook's first cell, the walkthrough and the README all opened with import gemstone — Grail's own lower-level surface, inherited from Jasper. gemdb is the module a developer should meet first:

# Python here runs inside your GemDB database.
# Everything reachable from gemdb.root is still there tomorrow.
import gemdb

gemdb.root["greeting"] = "Hello from GemDB!"
gemdb.commit()

gemdb.root["greeting"]

Run verbatim through the staged shell before being written down — the last line really does display 'Hello from GemDB!'. gemdb.root rather than a bare subscript is the better first impression anyway, because root is what makes persistence visible, and it matches docs/demo/, which already used gemdb.

gemdb is on the PATH of terminals opened in VS Code

The command is generated into ~/GemDB/bin, which is on nobody's PATH, so which gemdb answered "not found" and the README's answer was an export PATH=… line the user had to add to their own profile.

putCliOnPath contributes it to context.environmentVariableCollection. That stays on the automated side of the line in CLAUDE.md: it applies only to terminals this editor launches, VS Code reverts it when the extension is disabled, and nothing outside ~/GemDB is touched. Editing a shell profile would be the other side — persistent, global, not ours to undo — so the README still asks for that one.

clear() before every prepend, because the collection is persisted across window reloads and re-applied before activation: without it a reload stacks a second entry, and a changed gemdb.rootPath leaves the old one in front. Applied after the platform gate, and before bin/gemdb exists, so a terminal opened during first-run setup finds the command once setup writes it.

topaz no longer comments on an exit it was always going to ignore

gemdb file.py from a real terminal ended with:

Rabbit stowed.
topaz ignoring EXIT
Near line 87 of file /Users/jfoster/GemDB/bin/gemdb-run.tpz
 within INPUT or IFERR when interactive, stdin is a tty
The rest of the command (' 0') was ignored.
Logging out session 1.

topaz -h says of -S that topaz "exits when the script completes" and that "exit and quit commands are ignored". Ignored silently on a pipe — which is every CI run, both suites, and every gemdb x.py | cat — and out loud on a tty. The ignored EXIT is also what provoked the Logging out session 1.

Exit codes never travelled through that line and still do not, re-measured over a pty without it: sys.exit(3) → 3, uncaught exception → 1 with the message on stderr, missing file → 2, -c → 0. input() from a terminal still works.

Non-ASCII survives gemdb file.py in both directions

The console this mode installs is a GsFile, which takes bytes, and nextPutAll: writes a Unicode string's code units straight through. A in the demo's ASCII-art rabbit turned the whole drawing into binary:

$ gemdb reveal.py | cat -v
^@ ^@ ^@(^@\^@_^@/^@)^@

The #GrailConsole box gains a second slot naming what the sink takes. The sink cannot be asked: the shell's is a ClientForwarder, and any send to one — class, respondsTo:, isNil — forwards to the client as error 2336, which is not a Smalltalk exception and is not catchable in the gem (measured). A probe would turn every print in a streaming session into a spurious client stop.

⚠️ Needs GemTalk/Grail#701, which encodes when the slot says so — and fixes the mirror-image fault on the read side, where a line typed at the terminal arrived one character per byte (wörld six characters long, mojibake on the way back). CI here is red until that lands, since bundle:grail clones Grail's default branch; prove it green meanwhile with workflow_dispatch and grail-ref: fix/console-utf8-for-byte-sinks.

Verification

  • Unit: 122 passing. New tests for putCliOnPath (through a fake collection, in the StopWorld style), for the driver carrying no exit, and for the #'utf8' slot.
  • Integration, against a payload and extent rebuilt from the Grail branch: 48 passing, up from 46. The two new cases pin both directions of the encoding seam, and both fail with the marker removed — checked, so they are not vacuous.
  • Lint, format, and both typechecks green.

One commit rather than four: splitting it would have meant reconstructing intermediate versions of five files that every topic touches.

…rong

Four changes a user ran into in one sitting.

**The sample code says `import gemdb`.** A new notebook's first cell, the
walkthrough and the README all opened with `import gemstone` -- Grail's own
lower-level surface, inherited from Jasper. `gemdb` is the module a developer
should meet first: `gemdb.root` for the data that outlives the session,
`gemdb.commit()` for the moment it becomes everyone's.

**`gemdb` is on the PATH of terminals opened in VS Code.** The command is
generated into `~/GemDB/bin`, which is on nobody's PATH, so `which gemdb`
answered "not found" and the README's answer was a line the user had to add to
their own shell profile. VS Code contributes this per extension and reverts it
when the extension is disabled, which keeps it on the automated side of the
line; editing a shell profile would not be, so that stays an ask. Cleared
before every prepend, because the collection is persisted across window
reloads and re-applied before activation.

**topaz no longer comments on an exit it was always going to ignore.**
`gemdb file.py` from a real terminal ended with four lines about ignoring an
EXIT and a `Logging out session 1.`. `topaz -h` says of `-S` that topaz "exits
when the script completes" and that "exit and quit commands are ignored" --
silently on a pipe, which is every CI run and both suites, and out loud on a
tty. Exit codes never travelled through that line and still do not:
sys.exit(3) -> 3, an uncaught exception -> 1, a missing file -> 2, measured
again over a pty without it.

**Non-ASCII survives `gemdb file.py` in both directions.** The console this
mode installs is a GsFile, which takes BYTES: print() wrote a Unicode string's
code units, so a bullet in an ASCII-art rabbit came out as binary. The
#GrailConsole box gains a second slot naming what the sink takes, because the
sink cannot be asked -- the shell's is a ClientForwarder, and any send to one
forwards to the client as error 2336, uncatchable in the gem. Needs the
matching Grail change (GemTalk/Grail#701), which also decodes the mirror-image
fault on the read side: a line typed at the terminal arrived one character per
byte.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@jgfoster
jgfoster merged commit 70deadb into fix/restage-cli-when-stale Aug 27, 2026
6 of 10 checks passed
@jgfoster
jgfoster deleted the feat/gemdb-samples-path-and-encoding branch August 27, 2026 22:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant