Skip to content

fix: interpolation bold doubling and error recovery display fixes#115

Open
dvhthomas wants to merge 21 commits intomainfrom
feat/evaluator-error-recovery
Open

fix: interpolation bold doubling and error recovery display fixes#115
dvhthomas wants to merge 21 commits intomainfrom
feat/evaluator-error-recovery

Conversation

@dvhthomas
Copy link
Copy Markdown
Contributor

Summary

Follow-up fixes discovered during Lark playground testing of #112:

  • Bold doubling bug: {{var}} interpolation wrapped values in **bold**, causing ****value**** when users wrote **{{ var }}** in their markdown. Removed auto-bold — users control formatting in their source
  • Diagnostic line counter: Blank lines within calc blocks caused error diagnostics to appear on the wrong line (or not at all) in HTML output
  • Multiple semantic errors: Only the first semantic error per block was reported. Now all are collected (e.g., two variable redefinitions in one block both show)
  • Block-level error dedup: Per-line diagnostics no longer repeat as a block-level error at the bottom
  • Below-line error display: Error messages appear below the source line instead of inline to the right (prevents layout push)
  • Evaluator benchmarks: 100-statement doc: ~167µs, 50-error doc: ~154µs

Test plan

  • TestInterpolateLine_BoldSourceDoesNotDouble
  • TestHTMLFormatter_ErrorAfterBlankLine
  • TestHTMLFormatter_SemanticErrorShowsOnCorrectLine
  • TestMultipleSemanticErrors
  • BenchmarkEvaluate_ValidDocument / BenchmarkEvaluate_WithErrors
  • task test — 31 packages pass
  • task quality — all checks pass
  • Verified on lark.calcmark.org via local testing

Co-Authored-By: Claude Opus 4.6 (1M context) noreply@anthropic.com

dvhthomas and others added 21 commits April 6, 2026 12:19
…iagnostic code

- Add exported ErrPartialEvaluation sentinel error for partial evaluation detection
- Add unexported diagCodeCascadingError constant for cascading error diagnostics
- Add errors import to evaluator.go

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add erroredVars map[string]error field to Environment struct
- Add SetError, GetError, ClearError, ClearErrors methods
- Initialize erroredVars in NewEnvironment and Clone
- Clone copies erroredVars independently using maps.Copy
- Add 7 test scenarios covering happy paths, coexistence, and clone isolation

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…ifier check

- Add CascadingError type in errors.go with VarName, Cause, Unwrap()
- Modify evalIdentifier to check env.GetError() before env.Get()
- Errored variables produce CascadingError instead of returning stale values
- Tests: cascading error type, errors.As, precedence over values, binary op propagation, undefined not cascading

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Change the statement evaluation loop from break-on-error to continue-on-error:
- Failed statements get nil placeholders maintaining 1:1 results/statements alignment
- Cascading errors (from errored variable refs) get hint severity diagnostics
- Root-cause errors get error severity diagnostics with eval_error code
- Errored assignment variables are tracked via env.SetError() for downstream detection
- Block stays dirty when any statement fails; SetLastValue uses last non-nil result
- First error preserved in block.SetError() for legacy compatibility

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…EvaluateAffectedBlocks

All three evaluation entry points now continue past block errors,
collecting diagnostics across the entire document. Returns
ErrPartialEvaluation when any block had errors, nil otherwise.

- Evaluate(): tracks hasErrors, continues to next block on error
- EvaluateBlock() pass 1: parse/redefinition errors become non-fatal
- EvaluateBlock() pass 2: evaluateCalcBlockSelective gets statement-level
  recovery parallel to evaluateCalcBlockWithDoc (Unit 3)
- EvaluateAffectedBlocks(): continues past block errors
- Semantic checker receives errored variables so downstream blocks get
  CascadingError instead of undefined_variable
- Environment.GetAllErroredVars() added for checker population
- CLI eval.go handles ErrPartialEvaluation: formats output then exits 1
- convert.go handles ErrPartialEvaluation: returns partial output + error
- Downstream test updates for new error recovery behavior
- Golden test file updated for embedded block error formatting

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Update all 3 Evaluate() call sites in doceval to handle partial
evaluation gracefully instead of treating it as fatal:
- generateFullDoc: log warning, continue with partial results
- evalProgressive: continue with partial line results
- evalBlockIndependent: return BlockResult with partial error and lines

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add 3 golden .cm files exercising multi-block error recovery,
cascading errors, and mixed success/error scenarios. Add 3
integration tests validating the full evaluator pipeline:
- Multi-block: errored block, cascading block, independent block
- Cascading: root cause + 2 cascading within block + cross-block
- Mixed: div-by-zero mid-document with 4 successful + 4 errored vars
In evalAssignment, call env.ClearError after a successful Set to
prevent a variable that was previously errored from appearing as
a CascadingError when referenced later — even though it now has
a valid value. Found during reliability review.
- Only mark the conflicting variable as errored on redefinition,
  not all variables in the block (P1 correctness finding)
- Track non-recoverable blocks (parse/redefinition) separately so
  Pass 2 retries runtime-errored blocks with the reactive env,
  preserving forward-reference reactive semantics (P1 correctness)
- GetAllVariables() now returns a snapshot copy instead of the live
  map, preventing concurrent map read/write panics if callers hold
  the reference across evaluation cycles

- Statement eval loops now append nil placeholder for zero-result
  statements (e.g., directives), maintaining 1:1 alignment between
  results slice and AST nodes. Prevents the statement index drift
  class of bugs when directives are interspersed with calculations.
Verifies the TUI correctly distinguishes root-cause errors
(blocked=false) from cascading errors (blocked=true) using the
data-driven catwalk framework. Tests that:
- Division by zero shows as eval_error, not blocked
- Successful statements after errors show their values
- Cascading references show as cascading_error with IsBlocked=true
The Hugo render hook previously treated errors and results as
mutually exclusive -- if a block had an error, only the error was
shown and all results were hidden. With error recovery producing
blocks that have both partial results AND errors:

- Add LineResult.Error field to doceval so per-line errors are
  included in cm_results.json alongside successful results
- Update render-codeblock-calcmark.html to show a unified results
  table with successful values and inline error indicators
- Add CSS for .cm-partial-error and .cm-error-value styling
- Fatal errors (no results at all) still show the error-only view
The HTML formatter now shows errors inline next to each source line
instead of a single block-level error at the bottom:

- Root-cause errors: red "✗ division by zero" inline
- Cascading errors: amber italic "⚠ depends on errored variable"
- Successful lines: blue "= value" as before

Adds Error and IsCascading fields to TemplateLine, populates from
block diagnostics by line number. Updates both default.gohtml and
preview.gohtml templates.
When a calc block contains blank lines between statements, the
diagnostic Line number counts all source lines (including blanks),
but the HTML formatter and doceval only counted non-blank lines.
This caused errors after blank lines to silently show nothing.

Test: TestHTMLFormatter_ErrorAfterBlankLine proves the bug with
"b = \$23\n\na = 1 / 0" — the error on line 3 was not matched
because the counter only reached 2. Fix: count every source line.
Long error messages like "cannot reassign 'a' — variables are
immutable" were pushing content sideways when displayed inline
to the right of the source. Now errors appear as a compact
diagnostic line below the source, keeping the layout clean.

Applies to both default.gohtml and preview.gohtml templates.
The Hugo render hook (table-based) is unaffected.
When a semantic error (like variable redefinition) aborts a block
before interpretation, only the offending line gets a per-line
diagnostic. Other lines in the block show nothing — no result,
no error. Restore the block-level error div as a fallback so users
always see that the block failed, even for lines without per-line
diagnostics.

Test: TestHTMLFormatter_SemanticErrorShowsOnCorrectLine proves the
bug (semantic error diagnostic missing from HTML output) then
verifies the fix.
…xist

Add HasLineDiagnostic field to TemplateBlock. Templates only show
the block-level "Error:" div when no per-line diagnostics were
rendered, preventing the same error from appearing twice (once
inline, once at the bottom).

Text and markdown formatters retain the block-level error as-is —
they don't have per-line diagnostic rendering, so the block-level
error is their only error indicator.
The evaluator's semantic error handling returned on the first
error-severity diagnostic, missing subsequent errors like a second
variable redefinition in the same block. Now collects ALL semantic
error diagnostics before returning, so every redefinition or type
error is visible.

Both evaluateCalcBlockWithDoc and evaluateCalcBlockSelective are
fixed. Time complexity unchanged: O(n) for diagnostics, O(n) once
for variable-error marking.

Test: TestMultipleSemanticErrors proves that "a=1/0; a=2; c=3; c=5"
reports redefinition errors for both 'a' (line 2) and 'c' (line 4).
BenchmarkEvaluate_ValidDocument: 100-statement chain, ~167µs
BenchmarkEvaluate_WithErrors: 50 success + 50 cascading, ~154µs

Both well under the 15ms budget. Establishes a baseline for
detecting performance regressions from error recovery changes.
Interpolated values were wrapped in **bold** markers, causing
doubled markers (****value****) when users wrote **{{ var }}** in
their markdown. The user controls formatting in their source —
interpolation should just replace the tag with the value.

Test: TestInterpolateLine_BoldSourceDoesNotDouble proves the bug
with "We leave on **{{ out }}**" producing ****42****.
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.

1 participant