GV

GeezerVison

The World Wide Web, restored to 800 pixels of glory.

Home|About|Gaming cabinet|Archives|How it works|Starter sites

On this site

  • Sign in
  • ActionsCI/CD and automation
  • CodespacesCloud development environments
  • All Features
  • Documentation
  • Training
  • Certifications
  • Build with Copilot
  • Customize Copilot
  • Integrations
  • Trending repos
  • GitHub Sponsors
  • Developer articles
  • Enterprises

This view

  • Original page
  • New address

GeezerVison is showing github.com — Show states of named sets by their set in state hints and declared-here by CatarinaGamboa · Pull Request #517 · liquid-java/liquidjava

Conversation

@CatarinaGamboa

@CatarinaGamboa CatarinaGamboa commented Nov 10, 2026 •
edited
Loading

Uh oh!

There was an error while loading. Please reload this page.

Copy link
Copy Markdown
Collaborator

Closes #515

What and why

Since #511 the headline of a state error shows a state of a named set as a value of its set (lockState(d) == locked), but the hints and "Refinement declared here" still used the shorthand (locked, closed(this)). One message used several notations for the same states. Now:

  1. Hints name each state of a named set exactly as the headline does, with the receiver: lockState(d) == locked (PM decision). The sentences that said "d is X" or "needs it to be X" now state the condition: when it returns true, undoState(um) == undoable, this call needs undoState(um) == undoable, When doorState(d) == closed and lockState(d) == locked, d can call: .... In that form, a state of an unnamed set in the same class is shown as the headline shows it too, closed(d). Classes with no named set keep the current wording (In state created, f can call: ...).
  2. Alternatives in hint state lists are grouped: (a and b) or (c and d). This applies to unnamed sets too, since the problem is the same.
  3. "Declared here" stays verbatim (it is source). When the highlighted declaration writes states of named sets in the shorthand, one where line under it says what they mean, for those states only: where closed(this) means doorState(this) == closed, .... It is the same where style the alias notes of refinement errors use.

Display only: the VC, the verdict, which hints are given and the category do not change.

Same rule as the headline. Hints and the gloss both use the rule of StateValues.toSetForm. Its set lookup is now exposed as StateValues.setNameOf (the hints use it), and the gloss runs the error's own display rewrite on each state(arg) call in the declaration. So:

  • states of unnamed (stateN) sets keep the shorthand;
  • a state name shared by sets with different names keeps the shorthand, so no set is guessed. So does a set name shared by sets of different classes, e.g. a class and its superclass;
  • a negation is never turned into a value: hints describe the machine's states as before, and the gloss explains locked(this), never !locked(this), so !locked(this) reads as !(lockState(this) == locked). A declaration in value form (lockState(this) != locked) gets no gloss, since it already uses the headline's notation.

Before / after (CLI, ANSI colours stripped)

ErrorStateHintMultipleSets (sets doorState, lockState)

Before:

State Refinement Error: found doorState(d@26) == closed && lockState(d) == locked && doorState(d) == doorState(d@26) but expected doorState(d) == closed && lockState(d) == unlocked
26 |         ErrorStateHintMultipleSets d = new ErrorStateHintMultipleSets();
27 |         d.lock();
28 |         d.open(); // Expect: State Refinement Error
   |         ^^^^^^^^^
29 |     }
30 | }

Hints:
  - Call d.unlock() before this call to move d from locked to unlocked
  - In state closed and locked, d can call: unlock()

door/ErrorStateHintMultipleSets.java:28

--> Refinement declared here:
12 | 
13 |     @StateRefinement(from = "closed(this) && unlocked(this)", to = "opened(this)")
   |                              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
14 |     public void open() {}

door/ErrorStateHintMultipleSets.java:13

After:

State Refinement Error: found doorState(d@26) == closed && lockState(d) == locked && doorState(d) == doorState(d@26) but expected doorState(d) == closed && lockState(d) == unlocked
26 |         ErrorStateHintMultipleSets d = new ErrorStateHintMultipleSets();
27 |         d.lock();
28 |         d.open(); // Expect: State Refinement Error
   |         ^^^^^^^^^
29 |     }
30 | }

Hints:
  - Call d.unlock() before this call to move d from lockState(d) == locked to lockState(d) == unlocked
  - When doorState(d) == closed and lockState(d) == locked, d can call: unlock()

door/ErrorStateHintMultipleSets.java:28

--> Refinement declared here:
12 | 
13 |     @StateRefinement(from = "closed(this) && unlocked(this)", to = "opened(this)")
   |                              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
14 |     public void open() {}
  where closed(this) means doorState(this) == closed, unlocked(this) means lockState(this) == unlocked

door/ErrorStateHintMultipleSets.java:13

The same spec in value form with != (from = "doorState(this) == closed && lockState(this) != locked")

Before:

State Refinement Error: found doorState(d@26) == closed && lockState(d) == locked && doorState(d) == doorState(d@26) but expected doorState(d) == closed && lockState(d) != locked
26 |         ErrorStateHintMultipleSets d = new ErrorStateHintMultipleSets();
27 |         d.lock();
28 |         d.open(); // Expect: State Refinement Error
   |         ^^^^^^^^^
29 |     }
30 | }

Hints:
  - Call d.unlock() before this call to move d from locked to unlocked
  - In state closed and locked, d can call: unlock()

doorne/ErrorStateHintMultipleSets.java:28

--> Refinement declared here:
12 | 
13 |     @StateRefinement(from = "doorState(this) == closed && lockState(this) != locked", to = "opened(this)")
   |                              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
14 |     public void open() {}

doorne/ErrorStateHintMultipleSets.java:13

After (no gloss: the declaration is already in value form; the hints name the machine's states as before, only in the new notation):

State Refinement Error: found doorState(d@26) == closed && lockState(d) == locked && doorState(d) == doorState(d@26) but expected doorState(d) == closed && lockState(d) != locked
26 |         ErrorStateHintMultipleSets d = new ErrorStateHintMultipleSets();
27 |         d.lock();
28 |         d.open(); // Expect: State Refinement Error
   |         ^^^^^^^^^
29 |     }
30 | }

Hints:
  - Call d.unlock() before this call to move d from lockState(d) == locked to lockState(d) == unlocked
  - When doorState(d) == closed and lockState(d) == locked, d can call: unlock()

doorne/ErrorStateHintMultipleSets.java:28

--> Refinement declared here:
12 | 
13 |     @StateRefinement(from = "doorState(this) == closed && lockState(this) != locked", to = "opened(this)")
   |                              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
14 |     public void open() {}

doorne/ErrorStateHintMultipleSets.java:13

state_hint_study_undo_manager_error, sets named phase / undoState / redoState, with if (um.canRedo()) um.redo(); um.undo();

Before:

State Refinement Error: found (#canRedo_result ? redoState(um@27) == redoable : redoState(um@27) == notRedoable) && undoState(um@27) == undoState(#getUndoManager_result) && (#canRedo_result ? undoState(um) == undoable && redoState(um) == notRedoable && phase(um) == phase(#getUndoManager_result) : (#canRedo_result ? redoState(um) == redoable : redoState(um) == notRedoable) && phase(um) == phase(#getUndoManager_result) && undoState(um) == undoState(#getUndoManager_result)) but expected undoState(um) == undoable
26 |         CountingUndoManager um = frame.getUndoManager();
27 |         if (um.canRedo()) um.redo();
28 |         um.undo(); // Expect: State Refinement Error
   |         ^^^^^^^^^^
29 |     }
30 | 

Hints:
  - Guard this call with if (um.canUndo()): when it returns true, um is undoable
  - In state undoable and notRedoable or notUndoable and notRedoable, um can call: canUndo(), canRedo() (and possibly others)

undo/EditCommand.java:28

--> Refinement declared here:
62 | 
63 |     @StateRefinement(from="undoable(this)", to="notUndoable(this) && redoable(this)")
   |                            ^^^^^^^^^^^^^^
64 |     void undo();

undo/UndoManagerRefinementsExpert.java:63

After:

State Refinement Error: found (#canRedo_result ? redoState(um@27) == redoable : redoState(um@27) == notRedoable) && undoState(um@27) == undoState(#getUndoManager_result) && (#canRedo_result ? undoState(um) == undoable && redoState(um) == notRedoable && phase(um) == phase(#getUndoManager_result) : (#canRedo_result ? redoState(um) == redoable : redoState(um) == notRedoable) && phase(um) == phase(#getUndoManager_result) && undoState(um) == undoState(#getUndoManager_result)) but expected undoState(um) == undoable
26 |         CountingUndoManager um = frame.getUndoManager();
27 |         if (um.canRedo()) um.redo();
28 |         um.undo(); // Expect: State Refinement Error
   |         ^^^^^^^^^^
29 |     }
30 | 

Hints:
  - Guard this call with if (um.canUndo()): when it returns true, undoState(um) == undoable
  - When (undoState(um) == undoable and redoState(um) == notRedoable) or (undoState(um) == notUndoable and redoState(um) == notRedoable), um can call: canUndo(), canRedo() (and possibly others)

undo/EditCommand.java:28

--> Refinement declared here:
62 | 
63 |     @StateRefinement(from="undoable(this)", to="notUndoable(this) && redoable(this)")
   |                            ^^^^^^^^^^^^^^
64 |     void undo();
  where undoable(this) means undoState(this) == undoable

undo/UndoManagerRefinementsExpert.java:63

The fixture's two other errors change the same way: this call needs redoState(undoManager) == redoable, when it returns true, redoState(undoManager) == redoable, and where redoable(this) means redoState(this) == redoable.

Decided by the PM

  • Hints use the headline's exact form with the receiver (lockState(d) == locked), including grouped alternatives and the guard and "needs" sentences.
  • The gloss keeps the spec's own form with (this): where closed(this) means doorState(this) == closed, ....

Still open for the PM

  • Grouping. Parentheses appear only when there are several alternatives and an alternative has several states. The issue also suggests naming only the set that matters (e.g. only undoState). That would change which states a hint names, not only how they are written, so it is not in this PR.

IDE

Hints reach vscode-liquidjava as plain message text, so the new notation shows there with no change. The extension does not show the declaration excerpt, only a location link, so the gloss needs a server and client change: liquid-java/vscode-liquidjava#181 (label study).

Tests

The existing exact-message tests in TestStateHints are updated, and one test is added. Each one fails on main:

  • callSequenceNamesOnlyTheStateSetsItChanges: the hints for doorState/lockState (named sets), in the headline form.
  • queryWrittenWithItsStateSetIsOfferedAsAGuard: needed state and guard for set lock (named set, value-form spec).
  • joinsAreNotTakenForAnUnknownState: grouping, with unnamed sets, which keep the shorthand.
  • foundKeepsANamedStateSetByItsName: also checks the where line under the declaration.
  • New: setNameOfTheClassAndOfItsSuperclassKeepsTheShorthand (fixture ErrorStateSetNameInSuperclass), from Codex round 1. When a class and its superclass each have a set named phase, the headline (Show the states of named sets as values of their sets in state errors #511's rule) and the hints printed phase(c) == ready && phase(c) == busy, which reads as two values of one set. StateValues.setNameOf, shared by both, now keeps the shorthand in that case.

The other hint tests use unnamed sets and pass unchanged, so the shorthand is kept there. The full suite passes locally (728 tests).

🤖 Generated with Claude Code

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
@CatarinaGamboa @claude
Show states of named sets by their set in state hints and declared-here
6dd421a
The headline of a state error shows a state of a named set as a value of
its set, e.g. lockState(d) == locked. The hints now name such states the
same way (lockState == locked), and group alternatives in parentheses so
"and" and "or" cannot be misread. The declared-here excerpt stays as
written, with a "where" line under it saying what each shorthand state
in it means. Display only: the VC, verdict, hints chosen and category do
not change. States of unnamed sets, and names shared by sets with
different names, keep the shorthand, as in the headline.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
@CatarinaGamboa CatarinaGamboa added the study Needed before the error-message user study label Nov 10, 2026
@CatarinaGamboa

CatarinaGamboa commented Nov 10, 2026

Copy link
Copy Markdown
Collaborator Author

Clean-code review

Audit of this PR's new code only (clean-code-principles); the PM's small-PR rules take precedence.

  • StateRefinementError.java ON_A_NAME: [name-meaningful] the name does not say it matches a call. Fixed: renamed to CALL_ON_A_NAME (in the next push).
  • StateHints.forCall: [func-few-arguments] now 7 parameters. Declined: bundling them needs a new parameter class, which the small-PR rule rules out; the new one is a single function.
  • AuxStateHandler.stateHints: [core-dry] the set == state text repeats the shape toSetForm builds as an expression. Declined: the set lookup itself is shared (StateValues.setNameOf, which toSetForm now uses too); only the 3-character join is repeated, and building an expression to print it would be more code.
  • LJDiagnostic.getDeclarationText: [core-dry] reads the declaration's file again after getSnippet has read it. Declined: it runs only when an error is printed, and sharing the read would mean reshaping getSnippet.
  • StateRefinementError.getDeclarationNotes: [core-kiss] finds states with a regex over source text instead of the parsed predicate. Declined: the note explains the text as written in the excerpt, and the parsed from has this substituted and can be a disjunction of several refinements. Each match goes through the same display rewrite as the headline, so a call that is not a state of a named set gets no note.
  • StateHints.join: [func-single-purpose] names and groups at once. Kept: it is one short expression, and the grouping only makes sense together with the joining.

No SOLID or layering issues: the hint wording stays in StateHints, the set lookup in StateValues, and printing in LJDiagnostic.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@CatarinaGamboa

CatarinaGamboa commented Nov 10, 2026

Copy link
Copy Markdown
Collaborator Author

Codex review, round 1

  1. Inherited sets with the same set name give a false hint. If a class and its superclass each declare a set named phase (ready/done and busy/idle), a failed use() with from = "ready(this) && busy(this)" gets the hint In state phase == idle and phase == ready, which reads as an impossible value of one set. A shared state name across inherited sets gives an ambiguous ready and ready.
  2. Test gap: multiline declarations. getDeclarationText reads only the first line of the declaration, so a text-block from whose states are on later lines gets no gloss. The only gloss assertion uses a single-line string.
All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@CatarinaGamboa @claude
Keep the shorthand for a set name that sets of different classes share
9a1d86c
When a class and its superclass each have a set with the same name, their
states would read as values of one set (phase == ready and phase == busy),
in the headline and in the hints. They keep the shorthand instead.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
@CatarinaGamboa

CatarinaGamboa commented Nov 10, 2026

Copy link
Copy Markdown
Collaborator Author

Reply to Codex review, round 1

  1. Fixed (9a1d86c). Confirmed with the CLI: on this branch both the hints and the headline (the Show the states of named sets as values of their sets in state errors #511 rule) printed phase(c) == ready && phase(c) == busy. StateValues.setNameOf, which the headline and the hints share, now keeps the shorthand when sets of different classes have that set name. Now: found ready(c) && idle(c) but expected ready(c) && busy(c), with hints move c from idle to busy and In state idle and ready. The new test TestStateHints#setNameOfTheClassAndOfItsSuperclassKeepsTheShorthand (fixture ErrorStateSetNameInSuperclass) fails without the fix. A state name shared by inherited sets already kept the shorthand, both in the headline and in the hints, since Show the states of named sets as values of their sets in state errors #511 (several set names give no set).
  2. Declined. The excerpt itself shows only the first line of the declaration, and the gloss is meant to explain only the states shown in it (as the issue asks). A gloss for states on lines that are not shown would explain text the reader cannot see.

Full suite passes locally (728 tests).

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@CatarinaGamboa

CatarinaGamboa commented Nov 10, 2026

Copy link
Copy Markdown
Collaborator Author

Codex review, round 2

  1. Off by one in getDeclarationText. Codex says the Spoon end column is inclusive and substring's end is exclusive, so the last ) would be dropped. Then from = "closed(this) && unlocked(this)" would explain only closed(this), and from = "closed(this)" would get no note.
  2. Test gaps. Negation (from = "!ready(this)") and disjunction (from = "ready(this) || busy(this)") in a named set are not tested for message, note and hints together, and the superclass shared-name test does not check the declaration note.
All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@CatarinaGamboa

CatarinaGamboa commented Nov 10, 2026

Copy link
Copy Markdown
Collaborator Author

Reply to Codex review, round 2

No code changes this round.

  1. Declined, not a bug. Spoon columns are 1-based with an inclusive end. Converting to 0-based gives substring(column - 1, endColumn), whose exclusive end is exactly the inclusive 1-based end, as getSnippet already does for the ^ markers. The CLI output in the PR body shows both closed(this) and unlocked(this) explained. TestStateHints#foundKeepsANamedStateSetByItsName checks that line and passes.
  2. Declined (lean tests). The hints do not depend on how the spec is written: they name the machine's states, so a negated or disjunctive from takes the same path the updated tests cover. The negated form in the gloss is shown in the PR body (no note for value form; !locked(this) gets the note for locked(this)). Grouping of alternatives is covered by joinsAreNotTakenForAnUnknownState. The superclass case goes through the same setNameOf check for the note as for the hints, which the new test covers. The PM asked for only the tests that fail without the fix.
All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@CatarinaGamboa

CatarinaGamboa commented Nov 10, 2026

Copy link
Copy Markdown
Collaborator Author

Codex review, round 3

No findings. Codex rebuilt the verifier, ran TestStateHints and ran the CLI on fresh copies of the named-set and superclass cases. It found no correctness bug, no message that misstates what was checked, and no test gap that would fail without the fix. The review loop ends here.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@CatarinaGamboa @claude
Show states in hints in the headline's form, with the receiver
e2ce936
As the PM decided: a hint names a state of a named set as the message
does, lockState(d) == locked, and the sentences that said "d is" or
"needs it to be" a state now state the condition, e.g. "needs
undoState(um) == undoable". Classes without named sets keep the
shorthand wording.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
@CatarinaGamboa

CatarinaGamboa commented Nov 10, 2026

Copy link
Copy Markdown
Collaborator Author

Codex review, round 4

On the new diff (hints in the headline's form with the receiver, as the PM decided; commit e2ce936). No findings. Codex rebuilt the verifier and ran the CLI on fresh inputs: named sets, named and unnamed sets mixed in one class, and a field receiver this.door. Headline, hints and declaration note agreed in each case. The earlier declines were given to it and not repeated. The full suite passes locally (728 tests).

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@CatarinaGamboa @claude
Find the states the declaration explains in its AST, not its text
361d463
The note under "declared here" was found by matching calls in the source
line. It now walks the declaration's from as parsed, before states
written as values of their sets are read as the shorthand, so only a
state of a named set written as the shorthand is explained. The notes are
computed where the error is built and passed to it.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
@CatarinaGamboa

CatarinaGamboa commented Nov 10, 2026

Copy link
Copy Markdown
Collaborator Author

Codex review, round 5

On the AST-based declared-here note (commit 361d463). No correctness bug found. One test gap: the gloss assertion (ready(this)-style states) would also pass with the old regex. Codex suggests a test with from = "ready(old(this))", which the regex could not match. The CLI now prints where ready(old(this)) means phase(old(this)) == ready for it.

Declined: the regex is deleted, so a test that only fails against deleted code protects nothing going forward. old(this) in a from is also not a spec anyone writes. The existing assertion still checks the note end to end, as the PM asked. The full suite passes locally (728 tests).

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@CatarinaGamboa

CatarinaGamboa commented Nov 10, 2026

Copy link
Copy Markdown
Collaborator Author

Re: "why do we need CALL_ON_A_NAME if we have an AST already?"

You're right, it's gone (commit 361d463).

Why the regex existed. The note has to explain what the source wrote: closed(this) gets a note, doorState(this) == closed does not. By the time the error is built, the only AST at hand is the from after StateValues.toShorthand, which reads doorState(this) == closed as closed(this). That AST can't tell the two apart, so I scanned the text of the declaration instead. That was fragile: it depended on the printed line and on display leaving non-states unchanged.

What replaced it.

  • AuxStateHandler.getStates already parses the from string with the RJ parser before toShorthand runs. That parsed predicate is now kept on ObjectState as writtenFrom (and copied by clone).
  • Where the error is built, AuxStateHandler walks the FunctionInvocation nodes of the declaration's writtenFrom. It explains those that are a state of a named set, using StateValues.setNameOf/toSetForm, the same lookup the headline uses: where closed(this) means doorState(this) == closed, ....
  • The notes go to StateRefinementError as a list. LJDiagnostic only prints them and no longer reads or scans source text.

The output is unchanged for the PR's examples. A value-form from, ghost functions and aliases get no note. The full suite passes.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@CatarinaGamboa
CatarinaGamboa merged commit 538ac47 into main Nov 10, 2026
1 check passed

Uh oh!

There was an error while loading. Please reload this page.

This was referenced Nov 10, 2026
State hints: name only the state the call leads to #519
Closed
State hints: name only the state the call leads to, and no guard when a call sequence works #524
Merged
Show the message notes and the counterexample in the hover liquid-java/vscode-liquidjava#190
Merged
Show every declared-here position and its gloss in the IDE panel liquid-java/vscode-liquidjava#191
Merged
CatarinaGamboa added a commit that referenced this pull request Nov 10, 2026
@CatarinaGamboa @claude
State hints: name only the state the call leads to, and no guard when…
c4c6ce1
… a call sequence works (#524)

Closes #518
Closes #519

## What and why
Changes to the state hints in `StateHints` (display and choice of hints
only; the VC, verdict and category are unchanged):

1. **Call sequences name only the state they lead to** (#519): `Call S
before this call; afterwards <state>`. The "from" part is gone, since
the Found already shows it. In A1_09 it listed every state of the set
(`from display(host) == notDisplayable or display(host) ==
displayable`). "Afterwards" is the machine's `after` state for the
changed sets, so the hint only states what the machine computes after
the calls, and no `!=` is turned into `==`. The value form from #517
stays (`markState(buffered) == marked`). Classes with no named set read
`afterwards f is open`.
2. **A deciding argument is written as the call** (#519): when a step's
condition is `param == c` for the method's only parameter, and `c` names
no variable, the step is written
`wParam.setCompressionMode(ImageWriteParam.MODE_EXPLICIT)`. Any other
condition keeps `r.set(int lo, int hi) with lo < hi`.
3. **No GUARD hint when a CALL_SEQUENCE hint exists** (#518). Wrapping
the call in the check skips it in exactly the failing case. In A1_09 the
verifier accepts that fix and the study grader marks it INCORRECT.
Before, the guard was dropped only for a single known state. The
unknown-state hints (A1_56) do not change: its guard stays, since it has
no call sequence and the guard is the reference fix.
4. **Guard wording** (#518): `Guard this call with if (<check>); inside
it, <state>`. This replaces "when it returns false", which was ambiguous
next to `!host.isDisplayable()`.
5. **AVAILABLE_CALLS ("can call") is dropped when any other hint is
given** (#519). FINAL_STATE is unchanged.

## Study tasks (CLI, before = main, after = this branch)
Each task still gives exactly one error, on its `reportedLine`. A2_10,
A2_04, practice and practice2 are unchanged.

**A1_38**
```
before:
  - Call buffered.mark(int) before this call to move buffered from markState(buffered) == unmarked to markState(buffered) == marked
  - When stream(buffered) == open and markState(buffered) == unmarked, buffered can call: read(), read(byte[], int, int), skip(long), available(), mark(int), ...
after:
  - Call buffered.mark(int) before this call; afterwards markState(buffered) == marked
```
**A1_01**
```
before:
  - Call wParam.setCompressionMode(int mode) with mode == ImageWriteParam.MODE_EXPLICIT before this call to move wParam from compression(wParam) == compressionNotExplicit to compression(wParam) == compressionExplicit
  - When tiling(wParam) == tilingNotExplicit and compression(wParam) == compressionNotExplicit, wParam can call: setTilingMode(int), setCompressionMode(int) (and possibly others)
after:
  - Call wParam.setCompressionMode(ImageWriteParam.MODE_EXPLICIT) before this call; afterwards compression(wParam) == compressionExplicit
```
**A1_09**
```
before:
  - Call host.dispose() before this call to move host from display(host) == notDisplayable or display(host) == displayable to display(host) == notDisplayable
  - Call host.removeNotify() before this call to move host from display(host) == notDisplayable or display(host) == displayable to display(host) == notDisplayable
  - Guard this call with if (!host.isDisplayable()): when it returns false, display(host) == notDisplayable
  - When display(host) == notDisplayable or display(host) == displayable, host can call: pack(), dispose(), addNotify(), removeNotify() (and possibly others)
after:
  - Call host.dispose() before this call; afterwards display(host) == notDisplayable
  - Call host.removeNotify() before this call; afterwards display(host) == notDisplayable
```
`host.dispose()` is still the first hint. `analysis/check-fixes.py
--selftest --task A1_09` passes 8/8 and grades the plain `dispose()` fix
(P01) CORRECT.

**A1_56** (the guard stays)
```
before:
  - The state of undoManager is unknown here, and this call needs redoState(undoManager) == redoable
  - Guard this call with if (undoManager.canRedo()): when it returns true, redoState(undoManager) == redoable
after:
  - The state of undoManager is unknown here, and this call needs redoState(undoManager) == redoable
  - Guard this call with if (undoManager.canRedo()); inside it, redoState(undoManager) == redoable
```

## IDE
Hints reach vscode-liquidjava as plain message text, so they show there
with no extension change. There are no screenshots, because nothing in
the extension's layout changes.

## Tests
The exact-message expectations in `TestStateHints` are updated. Each of
these fails on main: the new wording, no "can call" next to another
hint, and `setCompressionMode(2)` / `chain.pick(1)` for the argument
form. One case is added to the existing `ErrorStateHintGuards` fixture:
`reachableOnEveryPath`, the A1_09 shape (path-dependent state, a call
that reaches the needed state, a query). On main it gives a sequence, a
guard and "can call"; now it gives only the sequence. The full suite
passes locally (728 tests).

## Open for the PM
- The unknown-state hints (`anyState`, used by A1_56) can also list a
call sequence after the guard. #518 scopes the rule to `forCall`, so the
guard is kept there. None of the study tasks has both.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Opus 5.5 <[email protected]>
CatarinaGamboa added a commit to liquid-java/vscode-liquidjava that referenced this pull request Nov 11, 2026
@CatarinaGamboa @claude
Show every declared-here position and its gloss in the IDE panel
7257b89
The server sends the verifier's declaration positions and declaration notes
(the "where ... means ..." lines) of refinement and state errors, and the
Locations section shows a link to every position, with the notes under them.

Needs liquidjava-verifier 0.0.38: getDeclarationNotes() comes from
liquid-java/liquidjava#517 and getDeclarationPositions() from
liquid-java/liquidjava#525.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
CatarinaGamboa added a commit to liquid-java/vscode-liquidjava that referenced this pull request Nov 11, 2026
@CatarinaGamboa @claude
Show the message notes and the counterexample in the hover (#190)
7886955
Closes #188

## What changes
The editor diagnostic that `LJDiagnosticsHandler` publishes now carries:
1. the headline (`<title>: <message>`), as before;
2. then each of the verifier's **message notes**,
`LJDiagnostic.getMessageNotes()`, one per line;
3. then the verifier's **counterexample**, `getCounterexampleStr()`,
when it has one.

That text is what the hover and the Problems view show. **Hints are left
out**, since they stay collapsed in the panel by design.

The server reads the verifier's generic `LJDiagnostic` API, not a
per-error list. So any note the verifier adds later appears in the hover
with no extension change. That includes the planned From note
(liquid-java/liquidjava#522), and a counterexample for state errors once
the verifier gives one.

## Before → after (hover text, verifier 0.0.37)
**A2_04**
```diff
  Refinement Error: expected timeout >= 0 but could not prove it
+ Counterexample: timeout == -1
```
**A2_10**
```diff
  Refinement Error: failures.size() == size(this.failures) && failures.size() >= 0 is not a subtype of failures.size() > 0
+ Counterexample: failures.size() == 0
```
A1_38, A1_01, A1_56 and A1_09 are unchanged. Their errors have no
message notes and no counterexample in 0.0.37 or main.

| | Before | After |
|---|---|---|
| A2_04 | <img
src="https://raw.githubusercontent.com/liquid-java/vscode-liquidjava/pr-screenshots/190/A2_04-before.png"
width="440"> | <img
src="https://raw.githubusercontent.com/liquid-java/vscode-liquidjava/pr-screenshots/190/A2_04-after.png"
width="440"> |
| A2_10 | <img
src="https://raw.githubusercontent.com/liquid-java/vscode-liquidjava/pr-screenshots/190/A2_10-before.png"
width="440"> | <img
src="https://raw.githubusercontent.com/liquid-java/vscode-liquidjava/pr-screenshots/190/A2_10-after.png"
width="440"> |

(Headless VS Code, 1600×900, Dark Modern.)

## Verifier version
**0.0.37**, already released. `getMessageNotes()` and
`getCounterexampleStr()` are both in it, and the branch builds with the
current pom. Today the only message notes are the `where <alias> means
…` lines of refinement errors that use aliases, and no study task has
one. The From note will come with a later verifier release.

## Open question for the PM
The issue lists the `where … means …` gloss among the notes. In the
verifier, the gloss is a **declaration note** (`getDeclarationNotes()`,
liquid-java/liquidjava#517, needs 0.0.38). It explains the "declared
here" excerpt, which the hover does not show. So this PR shows only the
message notes, as specified. If the hover should also show the gloss,
that is one more `lines.addAll(diagnostic.getDeclarationNotes())`, but
it needs the 0.0.38 bump that #191 makes.

## Overlap with open PRs
Server only, in `LJDiagnosticsHandler`. No conflict with #185, #186,
#189 or #191.

## Checks
Server `mvn -B test` (40 passed; new `LJDiagnosticsHandlerTest`, which
fails on main), `npm run test:unit` (124 passed), `npm run
test:typecheck`, `npm run lint`.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Opus 5.5 <[email protected]>
CatarinaGamboa added a commit to liquid-java/vscode-liquidjava that referenced this pull request Nov 11, 2026
@CatarinaGamboa @claude
Show every declared-here position and its gloss in the IDE panel (#191)
3a4d92f
Closes #181

## What changes
- **Server.** `RefinementErrorDTO` and `StateRefinementErrorDTO` now
send:
- `declarationPositions`, from `LJDiagnostic.getDeclarationPositions()`;
- `declarationNotes`, the `where … means …` lines, from
`getDeclarationNotes()`.

Both are read through the verifier's generic `LJDiagnostic` API. So when
the verifier adds notes or positions, they appear with no extension
change. `declarationPosition`, the first position, stays as it is for
the State Machine view.
- **Client.** The Locations section links to **every** declaration
position, then shows the notes under the links, muted and with a hanging
indent. A declaration in value form (`doorState(this) == closed`), or
one with unnamed sets, has no notes, so nothing changes for it.
- **Fallback to one position.** The verifier's default
`getDeclarationPositions()` returns the single
`getDeclarationPosition()`. Refinement errors and any other error with
one declaration therefore show one link, as before.

## Verifier version: needs 0.0.38 (not yet released)
| Part | Verifier change |
|---|---|
| `declarationNotes` (the gloss) | liquid-java/liquidjava#517 (merged in
main) |
| `declarationPositions` (every `from`, e.g. A1_01 lines 110 and 111) |
liquid-java/liquidjava#525 (**open**) |

The pom is bumped to `0.0.38`, so **CI's server build fails until 0.0.38
is on Maven Central**. Please merge this PR only after a verifier
release that has both #517 and #525. If #525 is not merged, drop the
`declarationPositions` lines from the two DTOs (one line each) and use
`declarationPosition` in `renderLocation` again. The gloss part only
needs #517.

I tested it locally against #525's head (`9d1bb1e`, which contains
main), installed as 0.0.38 in an isolated Maven repository.

## Before → after (Locations section, at the study width)
Before is main, with its server built on verifier main. After is this PR
on #525's head. Headless VS Code, 1600×900, Dark Modern, default side
bar.

**A1_01**: both `from`s, each with its gloss
```diff
  E_OSS_A1_01_SlideRecoder.java:136
  ImageWriteParamRefinements.java:110
+ ImageWriteParamRefinements.java:111
+ where compressionExplicit(this) means compression(this) == compressionExplicit
+ where compressionTypeSet(this) means compression(this) == compressionTypeSet
```
**A1_38**
```diff
  E_OSS_A1_38_FormatReaderManager.java:157
  BufferedInputStreamRefinements.java:81
+ where marked(this) means markState(this) == marked, open(this) means stream(this) == open
```
**A1_56**: `+ where redoable(this) means redoState(this) == redoable`
**A1_09**: `+ where notDisplayable(this) means display(this) ==
notDisplayable`
**A2_04**: unchanged. `@Refinement("_ >= 0")` has no gloss. The missing
Hint in the after shot comes from #525, not from this PR.

| A1_01 | A1_38 |
|---|---|
| <img
src="https://raw.githubusercontent.com/liquid-java/vscode-liquidjava/pr-screenshots/191/A1_01.png"
width="420"> | <img
src="https://raw.githubusercontent.com/liquid-java/vscode-liquidjava/pr-screenshots/191/A1_38.png"
width="420"> |

| A1_56 | A1_09 |
|---|---|
| <img
src="https://raw.githubusercontent.com/liquid-java/vscode-liquidjava/pr-screenshots/191/A1_56.png"
width="420"> | <img
src="https://raw.githubusercontent.com/liquid-java/vscode-liquidjava/pr-screenshots/191/A1_09.png"
width="420"> |

<details><summary>A2_04</summary>

<img
src="https://raw.githubusercontent.com/liquid-java/vscode-liquidjava/pr-screenshots/191/A2_04.png"
width="420">
</details>

The A1_01 and A1_09 shots are scrolled to Locations. The yellow outline
is the focus left on Hints by the screenshot script.

## Overlap with open PRs
- **#186 (Context tab)** may conflict in
`client/src/webview/views/sections.ts`. It changes
`renderVariableHighlightButton` and the imports, right after
`renderLocation`, which this PR edits.
- **#185 (Found as facts)** may conflict in
`client/src/types/diagnostics.ts`. Both PRs add fields to
`RefinementError` and `StateRefinementError`, in nearby hunks. No
conflict in logic.
- **#189 (panel wrapping)**: the notes are in the Locations `<pre>`,
which keeps `break-word`, so a long `compressionExplicit(this)` can
still wrap inside the name at 240 px (see A1_01). They do not share
files apart from `styles.ts`, in separate rules.

## Checks
`npm run test:unit` (125 passed; the new test fails on main and also
checks the copied text), `npm run test:typecheck`, `npm run lint`,
server `mvn -B test` (39 passed, against #525 as 0.0.38; the converter
test now also checks the notes and the positions).

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 5.5 <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Reviewers

No reviews

Uh oh!

There was an error while loading. Please reload this page.

Assignees

No one assigned

Labels

study Needed before the error-message user study

Projects

None yet

Milestone

No milestone

Development

Successfully merging this pull request may close these issues.

Use the state set notation of state errors in hints and declared-here

Uh oh!

There was an error while loading. Please reload this page.

1 participant

@CatarinaGamboa
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
You are visitor 01402503 — GeezerVison does not store the pages you look up.
Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required.