Skip to content

Commit ec9f80c

Browse files
nutyworksclaude
andauthored
Localize debugger labels and show enabled breakpoint counts (#101)
* Clarify breakpoint counts and localize debugger labels * fix: retain readable limit errors and focus native label coverage --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
1 parent f0f52ed commit ec9f80c

15 files changed

Lines changed: 487 additions & 33 deletions

File tree

‎docs/verification/features/breakpoints.md‎

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
Deleting from the condition modal waits for server acknowledgement before closing
66
back to its existing parent (including Source). Pending, rejected and unavailable
7-
requests remain visible in the same modal. The Active breakpoint list now only
7+
requests remain visible in the same modal. The breakpoint list now only
88
navigates: its row overflow, toggle, condition, delete and Undo actions are removed.
99
Function entries open their original source line and matching stage; block entries
1010
open an exact matching recorded/static stage in the current pause's Flow. Matching
@@ -62,8 +62,16 @@ appear on hover/focus; disabled markers also appear only on hover/focus, while
6262
enabled breakpoints remain visible. Flow keeps both saved disabled markers and unset breakpoint-capable stage affordances
6363
visible as neutral hollow circles/diamonds, including run and terminal function stages.
6464
Rendering an unset affordance does not create a saved breakpoint. The
65-
list retains enabled and disabled definitions with an explicit state label. The toolbar count and source stage summaries
66-
still count enabled breakpoints. Disabling preserves the saved condition; use the
65+
list retains enabled and disabled definitions with an explicit state label. Its header
66+
shows the total definitions and enabled count. The toolbar's short count and source
67+
stage summaries still count enabled breakpoints; the toolbar tooltip explicitly shows
68+
enabled and total counts. `CodonLabelsTest` checks enabled-only, disabled-only, mixed
69+
and filtered captions in English/Korean, alongside all Watch-kind prefix permutations.
70+
`CodonLabelsGameTest` retains one mixed snapshot per language at the 320×240 logical
71+
viewport, including the maximum-count header width, representative Watch Details
72+
and rotation. This produces eight screenshots rather than repeating every permutation.
73+
Limit failures retain client translation keys with a readable English fallback for
74+
console/RCON sources without client language assets. Disabling preserves the saved condition; use the
6775
list to navigate to its source, then hover/focus its original marker to enable it again. The whole-command
6876
marker at the front of the command-block editor is always visible, including when
6977
unused or disabled. Text selection in the
@@ -110,7 +118,7 @@ requirement, the loaded command-block path, `list` output and `/help` usage, wit
110118
test-registered `execute` tree instead of vanilla's. It does not execute a
111119
native loaded function or prove execution beyond the Source response limits.
112120

113-
The active list retains widget identity only for currently rendered controls.
121+
The breakpoint list retains widget identity only for currently rendered controls.
114122
Replacement authoritative snapshots, scrolling and empty/disconnected lists release
115123
obsolete labels, tooltips and actions; unchanged visible targets retain widget identity
116124
and focus restoration. `BreakpointListCacheTest` exercises 100 acknowledged replacement

‎docs/verification/features/watches-and-nbt.md‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,12 @@ edge, with no inline management controls. `WatchPanelLayoutTest` covers the same
1717
name/value layout at narrow and wide widths; `DebuggerCompactWatchGameTest`
1818
exercises the actual screen events and Details actions.
1919

20+
Watch Details uses localized kind prefixes and literal objective/storage/path identifiers.
21+
`CodonLabelsTest` checks EN/KO key and placeholder parity and all three Watch kinds,
22+
including `Health` and `Pos[0]`. `CodonLabelsGameTest` captures their actual English/Korean
23+
Details rendering and the localized yaw/pitch labels at normal and 320×240 logical
24+
viewports. These use presentation fixtures and do not establish server read execution.
25+
2026
## User path and expected result
2127

2228
Regular Watches default to 280 logical pixels (previously 360), bounded by the same

‎src/client/java/works/nuty/codon/client/ui/BreakpointListScreen.java‎

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -198,13 +198,19 @@ private void navigate(BreakpointTarget target) {
198198
graphics.fill(0, 0, width, height, DebuggerTheme.modalColor(0x70000000));
199199
graphics.fill(left, top, left + panelWidth, top + panelHeight, DebuggerTheme.modalColor(PANEL));
200200
graphics.outline(left, top, panelWidth, panelHeight, DebuggerTheme.modalColor(BORDER));
201-
WatchUi.line(graphics, font, tr(targets == null ? "codon.breakpoint.list_header" : "codon.breakpoint.saved_definitions_header", displayed.size()),
201+
WatchUi.line(graphics, font, countHeader(),
202202
left + 8, top + 10, panelWidth - 16, TEXT);
203203
if (displayed.isEmpty()) WatchUi.line(graphics, font, tr("codon.breakpoint.list_empty"), left + 12, top + 43,
204204
panelWidth - 24, MUTED);
205205
super.extractRenderState(graphics, mouseX, mouseY, partialTick);
206206
}
207207

208+
private String countHeader() {
209+
if (targets != null) return tr("codon.breakpoint.saved_definitions_header", displayed.size());
210+
long enabled = displayed.stream().filter(BreakpointDefinition::enabled).count();
211+
return tr("codon.breakpoint.list_header", displayed.size(), enabled);
212+
}
213+
208214
@Override public boolean mouseScrolled(double x, double y, double scrollX, double scrollY) {
209215
if (x < left || x >= left + panelWidth || y < top + 30 || y >= top + 30 + rows * ROW_HEIGHT
210216
|| scrollY == 0) return super.mouseScrolled(x, y, scrollX, scrollY);

‎src/client/java/works/nuty/codon/client/ui/DebuggerOverlay.java‎

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -386,7 +386,7 @@ private void renderHeader(GuiGraphicsExtractor graphics, DebuggerLayout layout,
386386
() -> { if (client.gui.screen() != null) client.gui.setScreen(new BreakpointListScreen(client.gui.screen(), state)); })
387387
.withTextIcon(DebuggerIcon.BREAKPOINT_LIST)
388388
.setTooltip(Tooltip.create(Component.translatable("codon.breakpoint.toolbar",
389-
breakpointCount)));
389+
breakpointCount, state.breakpoints().definitions().size())));
390390

391391
}
392392

@@ -731,8 +731,9 @@ private void renderSourceDetails(GuiGraphicsExtractor graphics, Bounds area, int
731731
}
732732
y = sourceValueRows(graphics, area, y, SourceDetailsFormatting.position(source),
733733
SourceDetailsFormatting.previousPosition(before, source));
734-
sourceValueRows(graphics, area, y, SourceDetailsFormatting.rotation(source),
735-
SourceDetailsFormatting.previousRotation(before, source));
734+
String yaw = tr("codon.ui.rotation.yaw"), pitch = tr("codon.ui.rotation.pitch");
735+
sourceValueRows(graphics, area, y, SourceDetailsFormatting.rotation(source, yaw, pitch),
736+
SourceDetailsFormatting.previousRotation(before, source, yaw, pitch));
736737
}
737738

738739
private int sourceValueRows(GuiGraphicsExtractor graphics, Bounds area, int y,

‎src/client/java/works/nuty/codon/client/ui/SourceDetailsFormatting.java‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,8 @@ public static String position(PauseSource source) {
1414
source.anchor().x(), source.anchor().y(), source.anchor().z());
1515
}
1616

17-
public static String rotation(PauseSource source) {
18-
return String.format(Locale.ROOT, "yaw %.1f° / pitch %.1f°", source.yaw(), source.pitch());
17+
public static String rotation(PauseSource source, String yaw, String pitch) {
18+
return String.format(Locale.ROOT, "%s %.1f° / %s %.1f°", yaw, source.yaw(), pitch, source.pitch());
1919
}
2020

2121
public static String previousPosition(@Nullable PauseSource before, PauseSource after) {
@@ -25,10 +25,10 @@ public static String previousPosition(@Nullable PauseSource before, PauseSource
2525
new String[] { "X", "Y", "Z" }, "%.2f");
2626
}
2727

28-
public static String previousRotation(@Nullable PauseSource before, PauseSource after) {
28+
public static String previousRotation(@Nullable PauseSource before, PauseSource after, String yaw, String pitch) {
2929
if (before == null) return "";
3030
return previous(new double[] { before.yaw(), before.pitch() },
31-
new double[] { after.yaw(), after.pitch() }, new String[] { "yaw", "pitch" }, "%.1f°");
31+
new double[] { after.yaw(), after.pitch() }, new String[] { yaw, pitch }, "%.1f°");
3232
}
3333

3434
private static String previous(double[] oldValues, double[] newValues, String[] labels, String format) {

‎src/client/java/works/nuty/codon/client/ui/WatchDetailsScreen.java‎

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,7 @@ private void rebuildLines() {
127127
retry.active = !entry.automatic() && !state.watchReadsFailed() && state.watches().canRetry(entryId);
128128
edit.active = expanded;
129129
add(WatchUi.text("details.expression"), TEAL);
130-
add(Component.literal(expression(entry.spec())), TEXT);
130+
add(expression(entry.spec()), TEXT);
131131
blank();
132132
add(WatchUi.text("details.binding"), TEAL);
133133
if (entry.spec().kind() == WatchSpec.Kind.STORAGE_NBT) add(WatchUi.text("details.storage"), TEXT);
@@ -151,12 +151,14 @@ private String fixedBinding(ClientWatchState.Entry entry) {
151151
return WatchUi.text("details.fixed", name + entry.spec().executor()).getString();
152152
}
153153

154-
private static String expression(WatchSpec spec) {
155-
return switch (spec.kind()) {
156-
case SCORE -> "score: " + spec.target();
157-
case ENTITY_NBT -> "entity NBT: " + spec.path();
158-
case STORAGE_NBT -> "storage NBT: " + spec.target() + " / " + spec.path();
154+
private static Component expression(WatchSpec spec) {
155+
String value = switch (spec.kind()) {
156+
case SCORE -> spec.target();
157+
case ENTITY_NBT -> spec.path();
158+
case STORAGE_NBT -> spec.target() + " / " + spec.path();
159159
};
160+
return WatchUi.text("kind." + spec.kind().name().toLowerCase(java.util.Locale.ROOT))
161+
.copy().append(": ").append(Component.literal(value));
160162
}
161163

162164
private static String value(WatchResult result) {

‎src/clientTest/java/works/nuty/codon/client/state/ClientBreakpointStateTest.java‎

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,22 @@
1414
import static org.junit.jupiter.api.Assertions.assertTrue;
1515

1616
class ClientBreakpointStateTest {
17+
@Test
18+
void acknowledgedMixedSnapshotKeepsDisabledDefinitionsAndEnabledCountDistinct() {
19+
var state = new ClientBreakpointState();
20+
var first = BreakpointDefinition.plain(BreakpointTarget.whole(
21+
new SourceLocation.Block(new BlockLocation(1, 64, 0, "minecraft:overworld"))));
22+
var second = BreakpointDefinition.plain(BreakpointTarget.whole(
23+
new SourceLocation.Block(new BlockLocation(2, 64, 0, "minecraft:overworld"))));
24+
assertTrue(state.acceptPage(1, 0, true, List.of(first, second.withEnabled(false))));
25+
assertEquals(2, state.definitions().size());
26+
assertEquals(1, state.definitions().stream().filter(BreakpointDefinition::enabled).count());
27+
assertFalse(state.get(second.target()).enabled());
28+
assertTrue(state.acceptPage(2, 0, true, List.of(first.withEnabled(false), second.withEnabled(false))));
29+
assertEquals(2, state.definitions().size());
30+
assertEquals(0, state.definitions().stream().filter(BreakpointDefinition::enabled).count());
31+
}
32+
1733
@Test
1834
void rejectsAnOversizedPagedSnapshotWithoutPublishingIt() {
1935
ClientBreakpointState state = new ClientBreakpointState();

‎src/clientTest/java/works/nuty/codon/client/ui/SourceDetailsFormattingTest.java‎

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,31 +12,41 @@ void coordinateChangeDoesNotProduceRotationHistory() {
1212
var before = source(7.5, -59.5, 5.5, 220.2f, 0);
1313
var after = source(-4.73, -60, 11.77, 220.2f, 0);
1414
assertEquals("X -4.73 / Y -60.00 / Z 11.77", SourceDetailsFormatting.position(after));
15-
assertEquals("yaw 220.2° / pitch 0.0°", SourceDetailsFormatting.rotation(after));
16-
assertEquals("", SourceDetailsFormatting.previousRotation(before, after));
15+
assertEquals("yaw 220.2° / pitch 0.0°", SourceDetailsFormatting.rotation(after, "yaw", "pitch"));
16+
assertEquals("", SourceDetailsFormatting.previousRotation(before, after, "yaw", "pitch"));
1717
assertEquals("← X 7.50 / Y -59.50 / Z 5.50", SourceDetailsFormatting.previousPosition(before, after));
1818
}
1919

2020
@Test
2121
void eachAxisAndAngleIsComparedIndependently() {
2222
var before = source(1, 2, 3, 45, -10);
2323
assertEquals("← X - / Y 2.00 / Z -", SourceDetailsFormatting.previousPosition(before, source(1, 4, 3, 45, -10)));
24-
assertEquals("← yaw - / pitch -10.0°", SourceDetailsFormatting.previousRotation(before, source(1, 2, 3, 45, 0)));
24+
assertEquals("← yaw - / pitch -10.0°", SourceDetailsFormatting.previousRotation(before, source(1, 2, 3, 45, 0), "yaw", "pitch"));
2525
assertEquals("", SourceDetailsFormatting.previousPosition(before, source(1, 2, 3, 90, 0)));
26-
assertEquals("← yaw 45.0° / pitch -10.0°", SourceDetailsFormatting.previousRotation(before, source(1, 2, 3, 90, 0)));
26+
assertEquals("← yaw 45.0° / pitch -10.0°", SourceDetailsFormatting.previousRotation(before, source(1, 2, 3, 90, 0), "yaw", "pitch"));
2727
}
2828

2929
@Test
3030
void missingParentOrUnchangedTransformDoesNotProduceHistoryRow() {
3131
var source = source(1, 2, 3, 45, -10);
3232
assertEquals("", SourceDetailsFormatting.previousPosition(null, source));
33-
assertEquals("", SourceDetailsFormatting.previousRotation(null, source));
34-
assertEquals("", SourceDetailsFormatting.previousRotation(source, source));
33+
assertEquals("", SourceDetailsFormatting.previousRotation(null, source, "yaw", "pitch"));
34+
assertEquals("", SourceDetailsFormatting.previousRotation(source, source, "yaw", "pitch"));
3535
assertEquals("", SourceDetailsFormatting.previousPosition(source, source));
3636
var otherDimension = new PauseSource(source.anchor(), source.pitch(), source.yaw(), null, "minecraft:the_nether");
3737
assertEquals("", SourceDetailsFormatting.previousPosition(source, otherDimension));
3838
}
3939

40+
@Test
41+
void localizedAnglesPreserveValuesAndIndependentHistory() {
42+
var before = source(1, 2, 3, 45, -10);
43+
var after = source(1, 2, 3, 45, 0);
44+
assertEquals("수평각 45.0° / 수직각 0.0°", SourceDetailsFormatting.rotation(after, "수평각", "수직각"));
45+
assertEquals("← 수평각 - / 수직각 -10.0°",
46+
SourceDetailsFormatting.previousRotation(before, after, "수평각", "수직각"));
47+
assertEquals("", SourceDetailsFormatting.previousRotation(null, after, "수평각", "수직각"));
48+
}
49+
4050
private static PauseSource source(double x, double y, double z, float yaw, float pitch) {
4151
return new PauseSource(new Vec3d(x, y, z), pitch, yaw, null, "minecraft:overworld");
4252
}

0 commit comments

Comments
 (0)