From d71e35a5aeb41cf98a8a8956a86ae61e822d0a78 Mon Sep 17 00:00:00 2001 From: Edgar Arakelyan Date: Mon, 9 Nov 2026 07:03:54 -0800 Subject: [PATCH] fix(desktop): find ids in archive entries and draft nothing for a state directory's lock (#9210) * fix(desktop): align nested state selections with aliases * fix(desktop): check build runtime export capability * fix(desktop): provide complete runtime repack command * fix(desktop): retain runtime probe failures * test(desktop): identify capture independently of schema preflight * fix(desktop): preserve document URLs and value formats * fix(desktop): bound added checks and omit noisy text guards * fix(desktop): exclude unseeded locks from task checks * fix(desktop): preserve draft bounds and launch URLs * fix(desktop): render draft values using their source format * fix(desktop): preserve launch URLs and JSON draft values * fix(desktop): recognize captured file URL documents * fix(desktop): render captured file URLs for runtime home * fix(desktop): distinguish phone numbers from save metadata * fix(desktop): preserve launch URLs and user text * fix(desktop): preserve captured URLs and user text * fix(desktop): preserve local file URL authority * fix(desktop): preserve local file URL authority * fix(desktop): preserve local file URL authority * fix(desktop): render captured file URLs at launch * fix(desktop): classify numeric timestamps using field context * fix(desktop): classify numeric timestamps using field context * fix(desktop): classify numeric timestamps using field context * fix(desktop): retain context for generated draft values * fix(desktop): retain context for generated draft values * fix(desktop): retain context for generated draft values * test(desktop): cover generated IDs in text guards * test(desktop): cover generated IDs in text guards * test(desktop): cover generated IDs in text guards * fix(desktop): isolate launcher URL encoding * fix(desktop): isolate launcher URL encoding * fix(desktop): isolate launcher URL encoding * fix(desktop): isolate launcher URL encoding * fix(desktop): retain clean text in noisy record fields * fix(desktop): classify added line values in one pass * fix(desktop): apply XML quote rules to text drafts * fix(desktop): distinguish XML attributes from quoted text * fix(desktop): preserve parsed XML line context * Format appTaskChecks.ts with the pinned oxfmt Co-Authored-By: Claude Opus 5.5 * fix(desktop): retain partial XML syntax facts * fix(desktop): recognize markdown autolinks in drafts * fix(desktop): reuse export exclusions in task drafts * fix(desktop): report unchecked client build paths * docs(desktop): clarify gzip scrub limits * fix(desktop): reuse export exclusions in task drafts * docs(desktop): clarify gzip scrub limits * fix(setup): own desktop build process cleanup * fix(doctor): compare desktop settings with resolved defaults * style(desktop): format the noisy-record draft files with the pinned oxfmt Co-Authored-By: Claude Opus 5.5 * test(desktop): give the root-array draft test its own describe The records describe grew past the 150-line lint limit with it. Co-Authored-By: Claude Opus 5.5 * fix(doctor): inspect client-recorded desktop runners * fix(doctor): reuse matching runner probes * fix(setup): propagate cancellation to desktop actions * fix(setup): stop Hopper on cancel, never report a cancelled setup ready, and keep a failed action's client setting - The Hopper install gets setup's signal: Linux already honoured it, and the macOS installer now stops its downloads and stops before mounting or copying. - runSetup checks the signal after the final doctor and close, so a cancellation there raises instead of returning ready. - A failed pack or agentenv install no longer writes its archive or runner to client configs, so the setting the client already had stays. Co-Authored-By: Claude Opus 5.5 * fix(setup): keep the completed setup result in a cancellation after cleanup A cancellation during the final doctor or host close carried only the applied actions, dropping each client's result and backup path and the doctor report. It now carries the whole result as setup_result, as doctor's cancellation carries its report. Co-Authored-By: Claude Opus 5.5 * test(desktop): move launch execution to boundary suite * fix(desktop): redact build schema failure output * refactor(desktop): normalize schema failure once * fix(desktop): omit unsafe schema parser snippets * fix(desktop): preserve draft change and selected path evidence * test(desktop): group retained dconf note regressions * fix(desktop): respect archive entry format in draft checks * test(desktop): separate unowned-file draft note cases * fix(desktop): preserve explicitly selected journal documents * fix(xml): avoid scanning selected ancestors per tag * fix(desktop): exclude covered text runs from draft counts * fix: preserve text when drafting desktop checks * fix: restore nested desktop state aliases * refactor: share UUID recognition across draft checks * fix: retain multiline XML attribute context * fix: retain relationship identifiers in task drafts * docs: clarify desktop draft identifier handling * test(desktop): type formatting run cases as pairs * fix(desktop): carry document format into selector evaluation * test(desktop): separate markup boundary regression group * fix(desktop): propagate formats through draft selectors * fix(desktop): switch selector format within archive entries * test(desktop): remove duplicate seed filename case * fix(setup): retain diagnostics when cancellation overlaps failure * refactor(setup): keep command cancellation at its owner * test(setup): verify cancellation at the application boundary * test(desktop): pass JSON format in exported index parity * docs(desktop): clarify task checks before scratch export * fix(desktop): respell selected paths in file URLs * test(desktop): group captured document spelling cases * fix(desktop): bound draft preview serialization * fix(desktop): restore selected state aliases * fix(desktop): respect entry format in draft noise * test(desktop): match exported alias evidence at its JSON boundary * fix(desktop): retain app state logs in exported seeds * fix: align desktop numeric seed comparisons * test(desktop): keep one draft exclusion fixture import * test(desktop): retain log eligibility across draft test groups * fix(doctor): resolve empty registered desktop runner * style: fix desktop launch test formatting * style: format native command path schema * fix(desktop): map selected external launch paths * style(desktop): format seed document regressions * test(desktop): group launch URL regressions * fix(desktop): omit redundant nested state aliases * fix(desktop): compare added free text as a whole * fix(desktop): check recompressed seeds as shipped * docs(desktop): clarify recovered seed format * test(desktop): group shipped gzip seed checks * fix: retain quoted identifier text in task drafts * fix: recognize Unicode XML in desktop state readers * fix(desktop): bound nested draft checks * fix(desktop): explain missing arrays in truncated views * fix(desktop): find XML type references in archives * test(desktop): remove duplicated capture assertions * fix(desktop): retain nested omitted text boundaries * style(desktop): format draft value imports * test(desktop): group model and draft value checks * fix(desktop): map captured launch paths once * refactor: remove unused whole-text guard state * refactor: stop passing unused text children * test: align free-text checks with whole-note policy * fix: validate recompressed desktop archive seeds * style(desktop): format archive seed regression * fix(desktop): retain placed-item sibling guards * fix(desktop): preserve archive paths and bound doctor reads * fix(doctor): refuse non-files before descriptor admission * fix(setup): track remaining cleanup resources across retries * test(desktop): specify XML format in heap pairing fixture * fix(doctor): query agentenv for model endpoint * fix(doctor): preserve uncertainty for empty endpoint override * fix(desktop): redact runtime schema errors * fix(desktop): normalize captured file URLs * fix(desktop): report seeded files by capture path * fix(desktop): retain startup readiness failure stage * fix(desktop): retain JSON date checks * fix(desktop): preserve archive formatting checks * fix(desktop): preserve explicitly selected journal files * fix(desktop): report build command transport limits * docs(desktop): clarify unreadable dconf versions * test(desktop): remove duplicate history regression * fix(desktop): align XML names with exported reader * fix(desktop): create selected state alias targets * fix(desktop): inspect supported runtime archives * fix(desktop): keep ordinary text checks exact * fix(desktop): verify registered build host paths * fix(desktop): retain record identity in draft checks * fix(desktop): scrub XML independently of reader compatibility * fix(desktop): preserve file aliases and reader diagnostics * style(desktop): format review fixes * fix(desktop): inspect archives with registered runner * test(desktop): verify registered archive interpreter * style(desktop): format capture review fixes --------- Co-authored-by: morluto <76467478+morluto@users.noreply.github.com> Co-authored-by: Claude Opus 5.5 --- docs/desktop-application-tasks.md | 4 +- src/domain/desktop/appTaskDraft.fixture.ts | 10 ++- src/domain/desktop/appTaskDraftFileNotes.ts | 8 ++- .../desktop/appTaskDraftLineGuards.test.ts | 62 ++++++++++++++++--- src/domain/desktop/appTaskDraftLineGuards.ts | 9 +-- src/domain/desktop/appTaskDraftModel.test.ts | 46 ++++++++++++++ src/domain/desktop/stateFormats.ts | 2 +- 7 files changed, 117 insertions(+), 24 deletions(-) diff --git a/docs/desktop-application-tasks.md b/docs/desktop-application-tasks.md index c08117aaf0..855c25c8c1 100644 --- a/docs/desktop-application-tasks.md +++ b/docs/desktop-application-tasks.md @@ -96,8 +96,8 @@ operation `describe_desktop_application_task` and predicate type in `.log` remain eligible for checks. A text file gains `lines_present` for its new lines and `lines_absent` for lines it lost (when either is at most ten), and `line_count` for each line - holding a letter whose count changed; a new line holding a value REA - recognizes as generated, such as a named epoch time or `ID_329023692`, gets a + holding a letter whose count changed; a new line holding an XML attribute + REA recognizes as generated, such as a named epoch time or `ID_329023692`, gets a `line_count` of the lines `contains` its text up to that value, as ` { }; describe("draftTaskFiles for lines holding values the app generates", () => { + it("keeps an unquoted assignment in ordinary prose exact", () => { + const line = "Set created=2025550123 and save"; + const file = drafted("# Instructions", `# Instructions\n${line}`); + expect(file.suggested).toEqual([ + { kind: "lines_present", file: FILE, lines: [line] }, + ]); + const checks = file.suggested.map((check, index) => ({ + ...check, + id: `c${index}`, + })); + const wrong = new Map([ + [ + FILE, + { + format: "text" as const, + view: textView("# Instructions\nSet created=WRONG and save"), + }, + ], + ]); + expect( + evaluateTaskChecks(checks, wrong).map(({ passed }) => passed), + ).toEqual([false]); + }); it("keeps assignment-like text quoted in a translation", () => { const translation = 'msgstr "Set created=2025550123 and save"'; const file = drafted(lines(), lines(translation)); @@ -304,21 +327,20 @@ describe("draftTaskFiles for added lines", () => { { kind: "lines_present", file: FILE, lines: [text] }, ]); }); - it("keeps phone numbers but counts a stable prefix before a named creation time", () => { + it("keeps assignment-like text exact when no XML structure identifies metadata", () => { const phone = 'topic TEXT="Call 2025550123"'; const timed = 'topic TEXT="Water filter" CREATED="1793955250671"'; const file = drafted(lines(), lines(phone, timed)); expect(file.suggested).toEqual( expect.arrayContaining([ - expect.objectContaining({ kind: "lines_present", lines: [phone] }), expect.objectContaining({ - kind: "line_count", - contains: 'topic TEXT="Water filter" CREATED="', + kind: "lines_present", + lines: [phone, timed], }), ]), ); }); - it("counts the lines holding a new line's text up to its first generated value, rather than the line whole, which another save would not repeat", () => { + it("keeps a non-XML line with ID-like assignments exact", () => { const file = drafted( lines(), lines( @@ -328,10 +350,11 @@ describe("draftTaskFiles for added lines", () => { expect(file.suggested).toEqual([ { - kind: "line_count", + kind: "lines_present", file: FILE, - contains: 'topic TEXT="Water filter" ID="', - equals: 1, + lines: [ + 'topic TEXT="Water filter" ID="ID_329023692" CREATED="1793955250671" MODIFIED="1793955252979"', + ], }, ]); expect(file.notes).toEqual([]); @@ -555,6 +578,29 @@ describe("draftTaskFiles for the app's log beside its journal", () => { }); }); +describe("draftTaskFiles for a lock the export leaves out of a state directory", () => { + it("drafts no check or guard on a lock directly in a state directory, which the export leaves out of the seed", () => { + const [lock] = draftOf( + { + ".config/notes/sync.lock": statePath( + "application_state", + "text", + textView("pid 1\n"), + textView("pid 2\n"), + ), + }, + [], + [".config/notes"], + ); + + expect(lock).toMatchObject({ + file: ".config/notes/sync.lock", + suggested: [], + guards: [], + }); + }); +}); + describe("draftTaskFiles guards of a line-keyed catalogue, as Poedit saves one", () => { it("guards the other records' lines of the changed line's keyword, never its own record's source reference or msgid", () => { const entry = (line: number, msgid: string, msgstr: string) => [ diff --git a/src/domain/desktop/appTaskDraftLineGuards.ts b/src/domain/desktop/appTaskDraftLineGuards.ts index bf354f22d2..481b2f34dc 100644 --- a/src/domain/desktop/appTaskDraftLineGuards.ts +++ b/src/domain/desktop/appTaskDraftLineGuards.ts @@ -141,11 +141,12 @@ const volatileAt = ( } } if ( - (isVolatileValue(token, key !== undefined && isBookkeepingName(key)) && + xmlAttribute && + ((isVolatileValue(token, key !== undefined && isBookkeepingName(key)) && !isUuid(token)) || - (key !== undefined && - /^(?:[^:]+:)?id$/iu.test(key) && - /^\p{L}+_\d{6,}$/u.test(token)) + (key !== undefined && + /^(?:[^:]+:)?id$/iu.test(key) && + /^\p{L}+_\d{6,}$/u.test(token))) ) return index; previous = token; diff --git a/src/domain/desktop/appTaskDraftModel.test.ts b/src/domain/desktop/appTaskDraftModel.test.ts index 40efc3b3d9..a545c8a76c 100644 --- a/src/domain/desktop/appTaskDraftModel.test.ts +++ b/src/domain/desktop/appTaskDraftModel.test.ts @@ -184,3 +184,49 @@ describe("draftTaskFiles for a zipped UML model", () => { ).toBe(false); }); }); + +describe("draftTaskFiles beside wide archive entries", () => { + it("drafts the zipped model's edit beside an entry holding a JSON array of 2,000,000 numbers, within the 4 MiB view limit", () => { + const ZIP = "Documents/library.zip"; + /** The model zipped as `library.xmi`, with `others` beside it. */ + const zipped = ( + text: string, + others: readonly JsonValue[] = [], + ): JsonValue => ({ + entries: [ + { + name: "library.xmi", + size: text.length, + sha256: "a".repeat(64), + format: "xml", + view: xml(text), + view_error: null, + }, + ...others, + ], + entry_count: 1 + others.length, + truncated: false, + }); + const wide = { + name: "samples.json", + size: 4_000_001, + sha256: "b".repeat(64), + format: "json", + view: Array.from({ length: 2_000_000 }, () => 0), + view_error: null, + }; + + const [file] = draftOf({ + [ZIP]: statePath( + "document", + "zip", + zipped(SEED, [wide]), + zipped(email(STRING), [wide]), + ), + }); + + expect(file?.suggested.map(pointerOf)).toContain( + `/entries/[name=library.xmi]/view${MEMBER}/children/[name=ownedAttribute][@name=email]/attributes/type`, + ); + }); +}); diff --git a/src/domain/desktop/stateFormats.ts b/src/domain/desktop/stateFormats.ts index ade457a55d..651710dfb3 100644 --- a/src/domain/desktop/stateFormats.ts +++ b/src/domain/desktop/stateFormats.ts @@ -34,7 +34,7 @@ const GVDB_HEADER = "GVariant"; const ZIP_HEADER = "PK\u0003\u0004"; const XML_EXTENSIONS = /\.(?:xml|xbel|plist|svg|ui)$/iu; /** A DOCTYPE that only names its DTD reads as XML; one with an internal subset, which can declare entities, does not. */ -const XML_POLICY = { +export const XML_POLICY = { doctype: "external", lineEndings: "normalize", nameSupported: expatXmlName,