Files
feuerwerk/src/editor/debug/documentHooks.ts
T
adminandClaude Opus 5.5 085e6c8cce
game-maker / gate (push) Successful in 3m10s
game-maker / suite (push) Failing after 8m55s
Keep edited list values when an item is deleted (#235)
The list tab read the list once when it built its rows. Editing a
value does not rebuild the rows, so the delete button later filtered
that old copy and wrote it back. Every edit since the last rebuild was
lost. Adding an item only triggered it, because adding rebuilds the
rows and that state became the old copy.

- ListAccess.value in src/editor/list/slot.ts is now read on every
  access instead of once when the access is built.
- The kind switch of a row and the struct field buttons (field type,
  rename, remove field, + Field) read the item when clicked, not
  when the row was built. They had the same stale copy.
- New test hook __gm.list.removeAt(i) clicks the delete button of one
  row, because the tab shows many buttons with the same label.

Not covered by a test of their own: the struct field buttons and the
kind switch after an edit. The cause is the same, the fix is in code.

Tests:
- ./check.sh: 2435 tests, CHECK EXIT 0.
- Unit test in tests/editor/list-slot.test.ts keeps one access, edits
  a value, adds and edits an item, deletes another. All edits stay.
  It fails without the fix.
- e2e 21b-ii with real clicks: set row 2 to 66, delete row 0, expect
  ["11","66"], two undos take both back. e2e 21: 52 checks passed.
- Counter probe: with the old snapshot the check fails with
  ["11","44"], the reported bug.
- Check total in README is now 1400 (+2), counted, not measured.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-26 14:45:23 +02:00

95 lines
5.6 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { findAsset, findAssetOf } from '../../core/model/Project';
import { previewObjectId } from '../../core/model/animationPreview';
import { currentFramesDialog } from '../animation/FramesDialog';
import { currentRowDialog } from '../animation/RowDialog';
import { centreOf, type HookContext } from './hookHelpers';
/** The Texture, Animation, Tile and List tabs. */
export function documentHooks({ state, editor }: HookContext) {
const { textureEditor } = editor;
return {
texture: {
current: () => textureEditor.currentAssetId,
/** Where an image pixel of the shown sheet sits on screen (frames are dragged in image coordinates). */
pointAt: (x: number, y: number) => textureEditor.imageToScreen(x, y),
rect: () => textureEditor.canvasRect(),
/** Whether a press edits the selected frame's collision outline (#141). */
collisionMode: () => textureEditor.canvas.collisionMode,
/** Rebuilds of the unselected frames' marks, their outlines, and where marks and names sit (#220). */
drawInfo: () => textureEditor.canvas.drawInfo,
/** The open "Generate collision…" dialog (#161), or null. */
generateDialog: () => textureEditor.generateDialog.current()?.info ?? null,
},
animation: {
current: () => editor.animationEditor.currentAssetId,
info: () => editor.animationEditor.shownInfo,
rootPickerAt: () => centreOf(editor.animationEditor.rootPicker),
addRowAt: () => centreOf(editor.animationEditor.addRowButton),
playAt: () => ({
...centreOf(editor.animationEditor.playButton),
text: editor.animationEditor.playButton.text,
}),
/** Where a cell, a row's header and a frame on the ruler sit – no text names a cell. */
cellAt: (trackId: string, frame: number) => editor.animationEditor.canvas.cellGlobal(trackId, frame),
headerAt: (trackId: string) => editor.animationEditor.canvas.headerGlobal(trackId),
rulerAt: (frame: number) => editor.animationEditor.canvas.rulerGlobal(frame),
keys: () => editor.animationEditor.canvas.keysDrawn(),
rows: () => editor.animationEditor.canvas.rowsDrawn(),
view: () => editor.animationEditor.canvas.viewInfo(),
scrub: (frame: number) => editor.animationEditor.canvas.scrub(frame),
/** The open row dialog: what it offers and holds, and a way to pick without clicking through menus. */
rowDialog: () => currentRowDialog()?.debugInfo() ?? null,
rowDialogPick: (target: string, property: string) => currentRowDialog()?.pick(target, property),
/** Copy/paste and frames (#102): what the clipboard holds, and the open frames dialog. */
clipboard: () => structuredClone(state.keyClipboard),
framesDialog: () => currentFramesDialog()?.debugInfo() ?? null,
framesDialogCount: (count: number) => currentFramesDialog()?.setCount(count),
framesDialogConfirm: () => {
const dialog = currentFramesDialog();
if (!dialog) return null;
const g = dialog.confirm.getGlobalPosition();
return { x: g.x + dialog.confirm.w / 2, y: g.y + dialog.confirm.h / 2 };
},
/** The preview above the timeline (#99): where a row's target is drawn and with which values. */
preview: {
/** The id the preview draws a row's target under (`''` = the root itself). */
objectId: (target: string) => {
const asset = findAssetOf(state.project, editor.animationEditor.currentAssetId, 'animation');
return previewObjectId(asset && findAsset(state.project, asset.root), target);
},
rect: (objectId: string) => editor.animationEditor.preview.objectRect(objectId),
item: (objectId: string) => editor.animationEditor.preview.itemDrawn(objectId),
info: () => editor.animationEditor.preview.debugInfo(),
fitAt: () => centreOf(editor.animationEditor.fitButton),
},
},
tile: {
current: () => editor.tileEditor.currentAssetId,
info: () => editor.tileEditor.shownInfo,
/** What the board drew: tiles, the picture it is showing and what stands in the way. */
board: () => editor.tileEditor.canvas.debugInfo(),
pictures: () => editor.tileEditor.strip.debugInfo(),
/** A tile of the board: its quarters and where each of them is drawn. */
tile: (index: number) => editor.tileEditor.canvas.tileDrawn(index),
/** Which board tile a cell of the sample map became – `-1` when that cell is empty. */
sampleTile: (row: number, column: number) => editor.tileEditor.canvas.sampleTile(row, column),
/** The captions under the grid: size, text and whether the zoom reaches their raster. */
captions: () => editor.tileEditor.canvas.captionInfo(),
kindPickerAt: () => centreOf(editor.tileEditor.kindPicker),
addPictureAt: () => centreOf(editor.tileEditor.strip.addButton),
removePictureAt: () => centreOf(editor.tileEditor.strip.removeButton),
pictureRowAt: (index: number) => editor.tileEditor.strip.list.rowGlobal(String(index)),
fitAt: () => centreOf(editor.tileEditor.fitButton),
},
list: {
info: () => editor.listEditor.debugInfo(),
/** Where an item's widget sits, so a check can click it – a number field has no findable text. */
fieldAt: (index: number, field = '') => editor.listEditor.fieldAt(index, field),
/** The kind mark of a row, or of one field of a struct item – both are pickers. */
markAt: (index: number, field = '') => editor.listEditor.markAt(index, field),
nameAt: (index: number, field: string) => editor.listEditor.nameAt(index, field),
removeAt: (index: number) => editor.listEditor.removeAt(index),
},
};
}