Files
reverse-engineering-anything/src/domain/desktop/appTaskDraftAlternatives.ts
T
61ab3f0c4f feat(desktop): name a record by a nested field or a child's attribute (#9185)
* fix(desktop): keep a selected folder that is named like a database log

A folder the caller selected stays a selected root when its name ends in
-journal or -wal. A generated log beside a database file still does not.

* fix(desktop): refuse a build command that exceeds the argument vector

Splitting one argument still leaves the whole command under Linux's E2BIG
limit. A request past that budget is refused before the process starts.

* fix(desktop): remap a captured path in an env assignment

The program word stays the program. A NAME=value assignment keeps its name
and follows a captured path value when that path's spelling moves.

* fix(desktop): keep oversized text out of draft guards

A value or line longer than the draft's text bound is not copied into a
guard, so one large frame cannot fill the task by itself.

* docs(desktop): correct dconf and check-preview guidance

A dconf version kept without its bytes is unreadable, not an empty key
list. A values preview is copied into a check only when it is an uncut scalar.

* fix(desktop): keep an empty env value empty

An empty assignment is not a path. Resolving it named the working directory
and rewrote the value when that directory's spelling moved.

* fix(desktop): keep a run's font on the run, not the frame's text

A formatting attribute such as Scribus FONT is not the document's words.
Joining it across runs made a frame-wide text check of ItalicBold that
failed when a run was split. The font stays a value check on its own run.

* fix(desktop): do not pair JSON records by a selector-shaped field

A field named @record_id matched the id suffix and became [@record_id=one],
which reads an XML attribute. Records paired while their pointers matched
nothing. Names that start with @ or > are not id fields.

* fix(desktop): count a save-numbered id once when pairing members

xml:id and draw:id values such as id123456 were both generated and
numbered, so the same selector was counted twice and refused. A text
edit of two elements keyed only by those ids now keeps its check.

* fix(desktop): count members of root JSON arrays

* fix(desktop): preserve selected state directory aliases

* docs(desktop): explain tool and skill refresh prerequisites

* 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 <noreply@anthropic.com>

* fix(desktop): retain partial XML syntax facts

* fix(desktop): recognize markdown autolinks in drafts

* fix(desktop): reuse export exclusions in task drafts

* docs(desktop): clarify gzip scrub limits

* style(desktop): format the noisy-record draft files with the pinned oxfmt

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* 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 <noreply@anthropic.com>

* 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

* 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

* 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

* test(desktop): specify XML format in heap pairing fixture

* 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): retain record identity in draft checks

* fix(desktop): scrub XML independently of reader compatibility

* style(desktop): format review fixes

* style(desktop): format capture review fixes

---------

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: morluto <76467478+morluto@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-09 23:01:22 +08:00

97 lines
3.3 KiB
TypeScript

import type { JsonValue } from "../jsonValue.js";
import { isStamp } from "./appTaskDraftNoise.js";
import {
attributeTier,
isIdentifyingAttribute,
isPlaceholder,
isSaveNumberedId,
} from "./attributeRank.js";
import { escapeToken, isXmlElement, resolvePointer } from "./viewPointer.js";
import { selectorOf, spellCondition } from "./viewSelector.js";
/** Other pointers a value's entry lists. */
const MAX_ALTERNATIVES = 3;
const unescape = (token: string) =>
token.replaceAll("~1", "/").replaceAll("~0", "~");
/**
* The value's pointer naming its element another way, for an author to pick
* a key without another export: the innermost member a pointer selects by a
* condition, or one inside it its tag alone names, as an EPUB chapter's `h1`
* by its `id`, by each other identifier or name-like attribute it holds, as
* a shape by `draw:name` beside its `xml:id`, that names it alone among its
* list's members of its tag, at most three. Never the attribute the pointer
* reads, an id an ODF app numbers anew on each save, a time or a placeholder.
*/
export const alternativePointers = (
view: JsonValue | null,
pointer: string,
): string[] => {
const segments = pointer.split("/");
for (let index = segments.length - 1; index >= 2; index -= 1) {
const conditions =
segments[index - 1] === "children"
? selectorOf(unescape(segments[index] ?? ""))
: undefined;
if (conditions === undefined) continue;
// A step its tag alone names, as an XHTML `h1` holding `id="chapter-two"`, gives way outward when nothing else names it.
const tagOnly = conditions.every(({ prefix }) => prefix === "");
const list = resolvePointer(
view,
segments.slice(0, index).join("/"),
"xml",
);
const element = resolvePointer(
view,
segments.slice(0, index + 1).join("/"),
"xml",
);
if (!Array.isArray(list) || !isXmlElement(element)) {
if (tagOnly) continue;
return [];
}
const used = new Set(
conditions.flatMap(({ prefix, key }) => (prefix === "@" ? [key] : [])),
);
const reads =
segments[index + 1] === "attributes" && index + 3 === segments.length
? unescape(segments[index + 2] ?? "")
: undefined;
const alone = (key: string, value: string) =>
list.filter(
(item) =>
isXmlElement(item) &&
item.name === element.name &&
item.attributes[key] === value,
).length === 1;
const found = Object.entries(element.attributes)
.flatMap(([key, value]) =>
typeof value === "string" &&
(isIdentifyingAttribute(key) || attributeTier(key, value) === 0) &&
!used.has(key) &&
key !== reads &&
!/[=[\]]/u.test(key) &&
!value.includes("][") &&
value.trim() !== "" &&
!isStamp(value) &&
!isPlaceholder(value) &&
!isSaveNumberedId(key, value) &&
alone(key, value)
? [
[
...segments.slice(0, index),
escapeToken(
`[name=${element.name}]${spellCondition({ prefix: "@", key, value })}`,
),
...segments.slice(index + 1),
].join("/"),
]
: [],
)
.slice(0, MAX_ALTERNATIVES);
if (found.length > 0 || !tagOnly) return found;
}
return [];
};