Skip to content

Improve note citations - #6

Open
luisherrmann wants to merge 110 commits into
developfrom
improve-note-citations
Open

Improve note citations#6
luisherrmann wants to merge 110 commits into
developfrom
improve-note-citations

Conversation

@luisherrmann

Copy link
Copy Markdown
Owner

Addresses #1, #2, #3.

luisherrmann and others added 28 commits August 12, 2026 01:31
A table continued on the next page arrived as two separate entries. Tables
now merge across a page break the way listings already do, keeping the first
or last fragment's caption. The shared grouping logic moves into
match-captions.js so both extractors use one implementation.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
… merge.

Extends the page-split table merge: a continuation whose caption carries an
explicit "continued" keyword is merged into the table it continues.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Replaces the "continued" keyword check with a comparison of caption label
numbers, so two fragments whose captions start with the same label ("Tab. 1")
merge regardless of what sits between them.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The width cap assumed a two-column layout and rejected legitimate regions in
single-column papers. Region plausibility is now decided without it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…ole.

A block was assigned to at most one region, so a listing spanning a column
break lost half its text. A block may now be claimed by several regions,
admitted only when it covers ground they do not already cover (see
BLOCK_NEW_COVERAGE_RATIO), and regions sharing a claimed block are merged
into one element.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Admits a contested block into a region on less new coverage than before.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The lower threshold let a block enter a neighbouring region as well as its
own, duplicating that text across two entries.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The area floor was chosen for figures that are a single large image, and
discarded the individual panels of a tiled figure -- which are much smaller
-- before the grouper could assemble them into one figure.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
… apart.

pairWithCaptions gains an absorbMargin option. Figures pass a larger value
than the default, since a multi-panel figure prints a label line under every
row and its panels sit further apart than a listing's fragments do.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The aspect cap was added for a single wide image taken to be a decorative
divider, which is in fact a display equation rendered as a raster. It also
discarded rows of side-by-side panels, which are inherently wide.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Figures sharing a block index came out in detection order rather than
document order. Ties now break on page, then vertical position, then
horizontal.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
… model.

Answers "list all figures" and similar by reading the extraction caches
directly and emitting a numbered list of ref: links, rather than leaving it
to the model and whatever subset of the paper fit its context.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…labels.

The pattern moves into match-captions.js and now accepts roman numerals and
single letters, guarded by single-case, canonical-numeral and I/V/X/L-only
tests so it cannot match an ordinary word.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
list_elements read the extraction caches through the raw accessors, but
cross-index deduplication runs in the pipeline and is also what rewrites
each disk cache -- so on a cold cache the tool reported the phantom entries
deduplication exists to remove. Figures stay on the raw accessor, since they
do not take part in deduplication.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Adds LLMIndexPipeline.ensureIndexed, wrapping buildIndexes plus the figure
and reference indexes. ui/index-all.js, _handleNormalChat and the tool
dispatch all call it, so a paper indexed in the background, mid-chat or by a
tool goes through the same path.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
figures.js wrote its disk cache and embedding rows eagerly, so it could not
take part in cross-index deduplication, which can only delete an entry
before it has been persisted.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…wn text.

Text found inside a figure's bounds is that image's own content -- UI
labels, diagram annotations, chart axis text -- not a listing. Such regions
are now removed in a pass after the caption contest, where the figure always wins.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…miss.

The pairwise tests ask only how much of a region sits inside one element, so
a region straddling two of them belonged to neither and survived. A third
pass removes a region whose own area is accounted for by the union of the
surviving elements around it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The geometric passes condemned an entire preformatted_id when any one of its
fragments overlapped, which deleted unrelated fragments of a listing split
across pages. They now mark individual fragments. The caption contest stays
group-level, where captioned-ness genuinely is inherited across fragments.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…misses.

A region routinely absorbs the descriptive line printed above its body, so
the separator between two listings ends up inside the later region rather
than between the two, where the intervening-prose test cannot see it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
startsWithProse accepted only paragraph blocks, but SDT types the same kind
of separator line as a caption in places, which left those listings merged
into one entry.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…late.

The candidate loop never declined a match, so once the nearest pairing was
made each remaining body took whatever caption was still free, at any
distance -- and since a match unions the caption's rect into the body's, a
distant caption inflated the region and swallowed its neighbour. A pair now
survives only if each side is the other's nearest on that page.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
SDT sometimes types a code comment as a paragraph, and startsWithProse read
it as the start of a new listing, severing one listing in two. Lines opening
with #, // or /* are excluded.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Each extractor's cache fingerprint covered only its own script, never
scripts/match-captions.js, which all three import and where most of the
churn lands -- so a change confined to that file left every cached index
serving its pre-change contents. Dependency tracking stays shallow: that
file's own imports are not stat'd.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…belled.

A paper may label its listings by name rather than by number, which the
enumerated-label pattern cannot match, leaving those labels invisible to the
caption machinery. Such a line is now split off the region it opens and
paired as a caption through the normal path, so the body begins at the code
block and captionBlockIndex < blockIndex holds -- which is what lets the
captions-precede-listings tally read the arrangement correctly.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
_captionLabel recognised only an enumerated caption, so a paper that titles
its listings rather than numbering them fell through to "Preformatted N"
even once its captions were extracted correctly. It now also reads the text before the colon as named label, leaving the description in `caption`.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The line it inspected is now recognised as the region's own caption, so the
caption test covers the case directly and the heuristic is redundant.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…guously.

A listing the paper never labelled was called "Preformatted N", which says
nothing about where it is -- two prompt templates in different appendix
subsections were indistinguishable except by number. It now carries its
nearest preceding section, the way extract-figures-sdt.js and
extract-tables-sdt.js already name an uncaptioned figure or table.

The counter stays GLOBAL rather than restarting per section, which is where
this departs from those two. A per-section counter makes the number
ambiguous on its own -- OSWorld's unlabelled figures produce "Unlabelled
Figure 1" twice, in different sections, so only the full label distinguishes
them, and the full label is exactly what is lost when it is abbreviated in
conversation or truncated for display, since the section prefix comes first
and the number is what gets cut. A global counter keeps the trailing number
unique paper-wide while the section still supplies context.

The section is resolved in the extractor, since the outline lives on the
structure and the plugin no longer has it when labelling a cached index, and
carried through as a sectionTitle field. The group's first fragment decides
it: a listing spanning a page break belongs to the section it starts in. A
listing with no outline entry before it keeps the bare name.

Labelling runs on the view rather than the raw index, so these names reach
neither the caches nor deduplication -- unlike the figure and table
equivalents, which copy the synthetic label into `caption` and need
_SYNTHETIC_LABEL_RE to tell it apart from a real one.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@luisherrmann luisherrmann self-assigned this Aug 26, 2026
@luisherrmann luisherrmann added the enhancement New feature or request label Aug 26, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant