Skip to content

Workbook Links: update linked workbooks from their files, change source, break links, startup prompt (builds on #464 and #829) - #1200

Open
Herr-Enklo wants to merge 16 commits into
storytold:mainfrom
Herr-Enklo:feat/workbook-links
Open

Herr-Enklo wants to merge 16 commits into
storytold:mainfrom
Herr-Enklo:feat/workbook-links

Conversation

@Herr-Enklo

Copy link
Copy Markdown

Builds on #464 by @neilcawse (formulas into linked workbooks calculate from the values saved with the link) and #829 by @marcoarnulfo (references typed by file name or path go through a link, fixes #804). Their commits are kept, with their authorship. #829's commits were cherry-picked onto current #464; conflict resolutions only adapt to main (external_books is an Arc now, WorkbookKept changed shape).

One reconciling commit lets #829's typed file names through #464's [n] entry check. [-1], [ 1] and [Book2] are still refused, as #464's review asked. It also fixes two printing issues: [1]!Rate printed as '[1]'!Rate, and link names were quoted twice.

What this adds: Excel's link management

  • Data › Queries & Connections › Workbook Links (Excel 365's task pane): Refresh All, Break All, search, and one card per linked workbook with path, status, last refresh time, and Refresh / Open Workbook / Change Source… / Break Links.
  • File › Info › Edit Links to Files, the classic dialog: Source / Type / Update / Status, Update Values, Change Source…, Open Source, Break Link, Check Status, Startup Prompt…
  • Update Values reads the source workbook with gridcraft-xlsx:
  • Check Status reports OK, Error: Source not found, Error: Worksheet not found, Warning: Values not updated, or Error: Undetermined. A missing source keeps the old values.
  • Change Source re-points a link. If another link already points at the same file, the two are merged.
  • Break Link turns every formula that reads the link (also through a defined name) into its value. Arrays become the values of their whole range; defined names keep the link. This matches what Excel does, observed black-box on synthetic workbooks. It is undoable, and the UI asks for confirmation.
  • Startup prompt is saved as workbookPr/@updateLinks (ECMA-376 ST_UpdateLinks); the default is not written.
  • Security, like Excel: opening a workbook never reads its links. A message bar says "Security Warning: Automatic update of links has been disabled" with Enable Content. This replaces the old "links … not followed or refreshed" open warning. The bar lives in a small reusable message_bar.rs.
  • XLSX:
  • Commands: data.workbookLinks, data.editLinks, data.refreshLinks, data.checkLinkStatus, data.changeLinkSource, data.openLinkSource, data.breakLink, data.linkStartupPrompt, data.enableLinkUpdates. The CLI and MCP reach all of them. In the browser, the file-reading commands are disabled with a reason.
  • Data tab: gets Excel 365's Queries & Connections group: a Refresh All split button; Queries & Connections, Properties and Workbook Links stacked beside it. Key tip A K runs Workbook Links. German labels follow German Excel (Arbeitsmappenlinks, Verknüpfungen bearbeiten, Werte aktualisieren, Quelle ändern…, Inhalt aktivieren…).

Expected overlap

The Get & Transform PR from the same fork also builds the Queries & Connections group and its own security bar. Whichever lands second needs a small reconcile; I'll rebase it.

Not included

Tests

🤖 Generated with Claude Code

neilcawse and others added 16 commits October 13, 2026 13:12
…with the link

A formula reading another workbook ([1]Prices!B2, '[2]P&L'!A1:B9, a name over
such a range) recalculated to #REF!: the externalLink parts were passed through
on save but never read. Excel calculates these from the cached copy of the
linked cells it saves in the file, as it does whenever the source is closed.
On a 4,224-file real-world corpus this was the largest root cause of
calculation mismatches.

The reader now loads each link's sheet names, cached cells, refresh-error
flags and defined names into Workbook::external_books; the lexer reads
unquoted [1]Sheet!A1 and [1]!Name; references, linked names and [0]!Name
evaluate from that data; the *IF(S) range arguments refuse
linked-workbook values and written errors, as Excel does with a closed
source; and linked sheets print unquoted when the sheet
name allows, so saved formulas keep their text.

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

References into a linked workbook became value arrays when evaluated, so `@`
and implicit intersection took the top-left value (`=@[1]Data!B:B` in row 4
gave the header), ROWS/COLUMNS/INDEX saw the block trimmed to the last cached
cell (`ROWS([1]Data!A:A)` = 3), and every lookup rebuilt the whole block.
They are now Areas whose sheet number (from LINKED_SHEETS up) stands for the
linked workbook and sheet; cells are read from the saved values only when the
reference is dereferenced (Evaluator::cell, Evaluator::block_values), and a
linked sheet's extent is remembered per pass like a local used range.

The printer dropped the quotes from '[1]Jan:Dec'!A1 and the lexer couldn't
read [1]Jan:Dec!A1. The lexer now accepts the unquoted MS-XLSX sheet-range
form, and the printer quotes a range when either sheet name needs it.
…]Jan:Dec

OFFSET took a reference into a linked workbook as its base and read the
saved link values. Excel errors there while the source is closed: the Enron
West NatGas Prices cells wrap OFFSET('[21]Curve Summary'!...) in ISERROR and
cached the backup value. OFFSET now answers #VALUE! for a linked base, the
answer of the other reference-only parameters given a closed workbook's values.

Renaming a local sheet rewrote the second end of [1]Jan:Dec!A1, which names a
sheet of the linked workbook; rename_sheet and delete_sheet now skip spans
whose first end is in a linked workbook.

Workbook::external_books is behind an Arc, like sheets and preserved parts,
so each edit's workbook clone and the undo history share the link cache.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ixes; [0] names stored local; CF/DV refuse other workbooks; paste into another workbook drops [n]
's [n] check

storytold#829 was written against an older storytold#464. Its typed references by file name or path
(`[Source.xlsx]Sheet1!A1`, `'C:\links\[Source.xlsx]Sheet1'!A1`) were refused by the
entry check storytold#464 added since (a bracketed prefix that isn't a link index). The check now
lets a typed file through where entry links it (`typed_book`), and linking runs last in
`input_to_cell`, so a formula refused for another reason never leaves a new link behind.
Conditional formats, data validation and paste still refuse other workbooks.

A typed file must look like one (a folder or an extension), so `[-1]Sheet1`, `[ 1]Sheet1`
and `[Book2]Sheet1` stay refused as storytold#464's review asked. `[1]!Rate` prints without quotes
around `[1]`, and the shown name of a link (`'C:\links\Source.xlsx'!Rate`) is quoted once
by the printer, as main now quotes sheet-qualified names itself.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…inks, startup prompt

The engine side of Data › Queries & Connections › Workbook Links and File › Info › Edit Links
to Files. Every action is a command, reachable from the CLI and MCP:

- data.workbookLinks / data.editLinks list the links (file, path, type, update, status, last
  refreshed, formulas using it) and ask the UI for the pane or the dialog.
- data.refreshLinks {link?} (Update Values / Refresh) reads the source workbook with
  gridcraft-xlsx: the cells and names this workbook reads from it, including whole columns
  trimmed to the source's used range, names used inside linked names, and 3-D spans. A source
  saved without values is calculated first. One undo step; dependents recalculate.
- data.checkLinkStatus: OK, Error: Source not found, Error: Worksheet not found, Warning:
  Values not updated (the file has other values than the link), Error: Undetermined.
- data.changeLinkSource {link, path} points the link at another file and reads it; onto a file
  another link reads, the two links become one.
- data.breakLink {link?}: as observed in Excel, every cell formula that reads the link, also
  through a defined name, becomes its value (arrays become the values of their range); names
  stay and keep the link. A link nothing names any more is removed and the later [n] move up.
- data.linkStartupPrompt {mode}: userSet / never / always, saved as workbookPr updateLinks
  (ECMA-376 ST_UpdateLinks); the default isn't written.
- data.openLinkSource opens the source; data.enableLinkUpdates is the security warning's
  Enable Content.

Opening a workbook never reads its links: like Excel, their automatic update is disabled until
Enable Content or Update Values (not at all when the prompt says never). Status and last refresh
are session state (DocState::links). Files are read through Session::link_files, the file system
off wasm; in the browser the commands that read sources are disabled with a reason.

Saving: ExternalBook::kept (replacing storytold#829's `added`) says which link part of the file holds a
link unchanged. Unchanged links are still written back byte for byte; a link added, refreshed or
moved to another source is written from the model in its place, and the part of a removed link
is dropped. Save As rebases relative paths of links written from the model too (storytold#824).

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

Data gets Excel 365's Queries & Connections group: the Refresh All split button (Refresh All,
Refresh, and Refresh Status, Cancel Refresh, Connection Properties… shown disabled), then
Queries & Connections, Properties (disabled) and Workbook Links, which is off while the workbook
links to no other workbook, as in Excel. Key tip A K now runs Workbook Links (it was Edit Links).

Workbook Links opens the task pane: Refresh All, Break All, a search box and a card per linked
workbook with its location, status (OK, Warning: Values not updated, Error: Source not found…),
last refresh and Refresh, Open Workbook, Change Source… and Break Links. File › Info shows
Related Documents › Edit Links to Files for a workbook with links, which opens the classic Edit
Links dialog (Source, Type, Update, Status; Update Values, Change Source…, Open Source, Break
Link, Check Status, Startup Prompt…). Break Link asks first; Change Source and Startup Prompt
open over Edit Links and return to it. All of it runs the data.* link commands.

Opening a workbook with links shows the message bar "Security Warning: Automatic update of links
has been disabled" with Enable Content. message_bar.rs is a small reusable mechanism: bars come
from the active document's engine state, each with a command button and a close button.

German labels follow German Excel (Arbeitsmappenlinks, Verknüpfungen mit Dateien bearbeiten,
Werte aktualisieren, Quelle ändern…, Quelle öffnen, Verknüpfung löschen, Status prüfen,
Eingabeaufforderung beim Start…, Sicherheitswarnung, Inhalt aktivieren). Generic words carry a
context (`Workbook Links|Source`) so they don't collide with other features' translations.

The open warning "links to external workbooks were kept; they are not followed or refreshed" is
gone: links are now refreshed on request, and the security warning says what Excel says.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
file-format-parity (externalLink parts written from the model when a link changes, workbookPr
updateLinks), gaps §12 (what works and what's left for links), ROADMAP progress log, ui-parity
and target-app-parity rows. An MCP test runs the link commands through execute_command. The
command-sequence fuzzer leaves out the link commands that read files or open a document, as it
does for file.open.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
neilcawse added a commit to neilcawse/gridcraft that referenced this pull request Oct 14, 2026
Main now quotes the sheet part of a sheet-qualified name, so after the rebase a name of a
linked workbook printed as '[1]'!Rate, in the formula bar and in saved files, where it names
a sheet called [1] in this workbook. Excel writes [1]!Rate. Same change as in storytold#1200.

Co-authored-by: Herr-Enklo <168219178+Herr-Enklo@users.noreply.github.com>

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

fix(formula): references to another workbook by file name or path are refused or #REF!, saved in a form Excel misreads, and shown as [1]

3 participants