Skip to content

Explorer: Rename Class Category + Rename Dictionary (R6) - #402

Merged
ericwinger merged 8 commits into
mainfrom
eric/RBrenameCategoryDictR6
Aug 11, 2026
Merged

ericwinger merged 8 commits into
mainfrom
eric/RBrenameCategoryDictR6

Conversation

@ericwinger

Copy link
Copy Markdown
Member

Adds two GemStone Explorer operations (catalog item R6): Rename Class Category and Rename Dictionary. Neither recompiles or commits — the user commits the session.

Rename Class Category

Right-click / pencil on a category node. Renames that single node of the dash-segmented category tree and carries its whole subtree along, server-side via Class>>category: (queries/renameClassCategory.ts). Category names are compared by Symbol identity (asSymbol ==), never String =, to dodge the 3.6.x wide-vs-narrow ArgumentError 2718. Non-ASCII names are blocked below 3.7 (same boundary as server UTF-8 file-in). An empty overlay-only category is carried across in the client overlay.

Rename Dictionary

Right-click / pencil on a dictionary node. A SymbolDictionary has no name slot — its name is a self-referential entry it holds by identity — so the rename is a reflective swap (add the new self-entry, drop the old); the symbol-list index is unchanged and the classes it holds are untouched (queries/renameDictionary.ts). System dictionaries (Globals / Published / UserGlobals) and name collisions are refused. Source that names the dictionary literally is not rewritten (rare; the confirmation says so).

Consistency with main

Both new queries resolve their dictionary through the shared dictLookupExpr helper (from #397) rather than hand-rolling symbolList at:/objectNamed:, so the noHandRolledDictLookup guard test passes. dictLookupExpr's numeric branch is made uniformly nil-on-absent (matching its name branch) so an out-of-range index answers nil instead of raising.

Review addressed (pre-PR)

A review of this branch was folded in before opening:

  • MED-1 — after a dictionary rename, stale editor tabs still name the old dictionary in their URIs; close the clean ones and warn about unsaved ones.
  • MED-2 — parse renamed: <n>; an unrecognised payload is an error, and 0-moved-while-classes-expected surfaces a warning, not false success.
  • MED-3 — always run the category-rename query rather than gating on a possibly-stale client cache.
  • LOW-1 — a category named exactly <old>- now moves with its subtree (>=).
  • LOW-2 — classes whose category can't be read are counted and reported (renamed: N skipped: M).
  • LOW-3/4/5/6 — category-rename right-click entry; a tautological test assertion; retain selection when renaming the current dictionary; document the classLookupExpr index-branch guard asymmetry.

Tests

Unit tests for both queries and both command handlers (including the MED-2 warn/error paths and the MED-1 tab sweep), plus live-stone integration tests that drive real renames. Emitted Smalltalk is ASCII-only (a non-ASCII char in a doit trips the 3.6.x ComStrmSetCursor compiler bug). Full local suite: client 5267 · server 322 · mcp 92; lint / prettier / tsc clean.

Note: verified locally on a 3.6.2 stone; a 3.7.5 pass is worth it in CI since the category path leans on Class>>category: and symbol interning, which shifted between those releases.

🤖 Generated with Claude Code

ericwinger and others added 5 commits August 10, 2026 13:26
Rename class category (the R6 catalog gap) and rename dictionary, both as
non-committing server metadata operations via the GemStone Explorer — no RB
engine clone, nothing recompiled or committed.

Rename class category:
- queries/renameClassCategory.ts reassigns the exact category and its
  dash-segmented subtree via Class>>category:. Category comparison uses
  Unicode-safe `asSymbol ==` (3.6.x raises error 2718 on String = of a wide
  category), so an ASCII rename is safe even when the dictionary contains a
  wide-named category.
- Handler renames a single node only: prompt is seeded with the last segment,
  dashes are rejected, and the full path is rebuilt from the unchanged parent —
  a rename can't graft new subtree levels or leave a trailing-'-' empty node.
- Version-aware non-ASCII guard: pre-3.7 stones can't compile a doit whose
  source carries a wide string literal (error 1001, ComStrmSetCursor), so on
  <3.7 a non-ASCII old/new name is refused with a clear message instead of a
  compiler crash; 3.7+ (the server-UTF8 boundary) proceeds normally.
- Pencil-only: the right-click menu entry is removed, matching PR #397.

Rename dictionary:
- queries/renameDictionary.ts swaps the self-referential association in place
  (keeps symbol-list position), guards system dictionaries and name collisions.

Tests: query + handler unit tests (single-node restriction, parent rebuild,
no-dash and non-ASCII validation, version gating) and live integration tests
for both category and dictionary rename.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…DictR6

# Conflicts:
#	client/src/__tests__/explorerQueries.integration.test.ts
#	client/src/gemstoneExplorer.ts
…on-absent

Follow-up to the dict-lookup consolidation now on main: swap renameClassCategory
and renameDictionary off the hand-rolled `symbolList at:/objectNamed:` ternary to
the shared dictLookupExpr helper, so the noHandRolledDictLookup guard test (merged
from main) passes on this branch too.

The swap exposed a regression: renameDictionary has an out-of-range-index ->
'Dictionary not found' test, but dictLookupExpr's bare numeric `at:` raised on a
bad index instead of returning nil. Make the helper uniformly nil-on-absent
(`at: N ifAbsent: [nil]`), matching its name branch, so callers' `ifNil:` guards
work for both forms. The output is still a substring of `symbolList at: N`, so the
existing swept-query assertions are unaffected.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
MED-1: after a dictionary rename, sweep open editor tabs whose URIs still name the
old dictionary -- close the clean ones, warn about dirty ones (new
closeStaleTabsForRenamedDictionary, reusing listOpenGemstoneTabs/parseUri).
MED-2: renameClassCategory now parses `renamed: <n>` -- an unrecognised payload is an
error, and 0 moved while the client expected classes surfaces a warning, not a false
success.
MED-3: always run the category-rename query instead of gating on the possibly-stale
classCategoryEntries cache.
LOW-1: `cat size >= prefix size` so a category named exactly `<old>-` moves with its
subtree instead of being left behind.
LOW-2: count classes whose category can't be read and report `renamed: N skipped: M`;
the handler warns when any were skipped.
LOW-3: add the 1_modification@0 right-click entry for renameClassCategory, matching
renameDictionary.
LOW-4: replace a tautological `expect(out).toBe('ok')` with an assertion on the
emitted code.
LOW-5: renaming the selected dictionary retains the class/method selection via
refreshRetainingSelection instead of selectDict.
LOW-6: document why classLookupExpr's index branch intentionally doesn't guard, unlike
the nil-safe dictLookupExpr.

The emitted Smalltalk is kept ASCII-only (rationale moved to TS comments) so the doit
doesn't trip the 3.6.x compiler (ComStrmSetCursor, err 1001). Full suite green:
client 5267, server 322, mcp 92.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

@MatiasFernandez MatiasFernandez left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Left some comments worth reviewing

Comment thread client/src/queries/util.ts Resolved
Comment thread client/src/queries/renameDictionary.ts Resolved
Comment thread client/src/queries/renameDictionary.ts Resolved
Comment thread package.json Resolved

@ericwinger ericwinger left a comment

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the thorough pass. All nine comments addressed — replies inline on each. Summary: guarded the category write (#1) and widened the dictionary type guard (#3); kept dictLookupExpr nil-on-absent for user-friendliness and reconciled both helper docs (#2); comment/manifest polish (#4, #9); and added the missing coverage — the tab-sweep suite (#5), the overlay-carry + classCategory remap case (#6), a live-stone contents-reachability assertion (#7), and split the cancelled/unchanged test (#8).

ericwinger and others added 2 commits August 11, 2026 11:43
- renameClassCategory: guard the `category:` write in `on: Error do:` and
  count failures into `skipped`, so one un-reassignable class degrades to
  `renamed: N skipped: M` instead of unwinding the batch and half-renaming
- renameDictionary: reject a non-SymbolDictionary target with a tidy refusal
  instead of a raw DNU; name the `name:`/`removeKey:` selectors in the comment
- util: keep dictLookupExpr numeric branch nil-on-absent (avoids a raw
  index-out-of-range walkback on a stale tree) and reconcile the dictLookupExpr
  / classLookupExpr docs so they explain the deliberate divergence
- tests: focused closeStaleTabsForRenamedDictionary sweep suite; overlay-carry
  + state.classCategory remap case; live-stone contents-reachability assertion
  for renameDictionary; split the cancelled/unchanged category test
- package.json: pin removeDictionary inline@1 after the rename pencil

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@ericwinger
ericwinger added this pull request to the merge queue Aug 11, 2026
Merged via the queue into main with commit a9fe57f Aug 11, 2026
15 checks passed
@ericwinger
ericwinger deleted the eric/RBrenameCategoryDictR6 branch August 11, 2026 18:57
ericwinger added a commit that referenced this pull request Aug 11, 2026
Brings in PR #402 (R6 rename category/dictionary), #404 (dedup
ownInstVarsOf:/allInstVarsOf: onto GsRefactoringEnvironment) and #405
(Unicode7 doit-literal fixes), plus the npm supply-chain hardening.

One conflict: resources/refactoring/manifest.gs, on the
GsRefactoringEnvironment method count -- this branch had 22 (it adds one
environment method), main had 23 (#404 added two). NEITHER side is correct for
the merged source, which defines 24; taking either would fail the loader's
post-load method-count check. Resolved by regenerating all payloads from the
merged gs-src via build-refactoring.sh rather than hand-picking a number, so
engine.gs / engine-tests.gs / manifest.gs are consistent with the source they
are built from. Verified: the only per-class manifest change against main is
GsRefactoringEnvironment 23 -> 24 (+1 overall).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
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.

2 participants