-
Notifications
You must be signed in to change notification settings - Fork 1
feat: preserve metric superscript and subscript semantics #344
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
922a384
efcf169
168d401
0291698
9c70238
0d1e6d1
96af13c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,50 @@ | ||
| # ADR 0105: Preserve explicit metric scripts in semantic text | ||
|
|
||
| **Status:** Accepted on this PR; not protected-main truth | ||
| **Date:** 2026-08-21 | ||
| **Owners:** LineageWeave ingestion and buyer-surface maintainers | ||
|
|
||
| ## Context | ||
|
|
||
| Source posts commonly encode a unit such as `m<sup>3</sup>`, `m<sub>3</sub>`, | ||
| `m^3`, or `m_3` with HTML or plain-text notation. Dropping the markup changes | ||
| the searchable meaning to `m3`, while treating every numeric `sup` element as | ||
| mathematics would break the existing numeric-footnote contract. Full MathML | ||
| parsing is not yet justified by the current product surface, but the loss of | ||
| explicit unit scripts is a buyer-visible defect. | ||
|
|
||
| MathML 4 defines `msup`, `msub`, and `msubsup` as structural script elements; | ||
| HTML `sup`/`sub` are a permitted lighter-weight notation when detailed | ||
| mathematical markup is not required. This decision therefore adds a bounded | ||
| normalization boundary and keeps the source representation unchanged. | ||
|
|
||
| ## Decision | ||
|
|
||
| 1. Preserve the immutable source body exactly as imported. | ||
| 2. In derived semantic text only, normalize an explicitly bounded metric base | ||
| (`m`, `cm`, `mm`, `km`, or `kg`, optionally preceded by a number) followed | ||
| by numeric `sup`/`sub` markup or plain-text `^`/`_` notation into Unicode | ||
| superscript/subscript digits. For example, `5m<sup>3</sup>` and `5m^3` | ||
| become `5m³`, while `m<sub>3</sub>` and `m_3` become `m₃`. | ||
| 3. Keep ordinary numeric superscripts and caret expressions on prose under the existing footnote | ||
| role contract. Do not infer a mathematical formula from an arbitrary word. | ||
| 4. Apply the same bounded normalization in backend semantic chunks and the | ||
| React buyer display so search text and visible text agree. | ||
| 5. Defer full MathML/LaTeX parsing, expression trees, and ontology term | ||
| creation until an authorized fixture demonstrates a need beyond metric | ||
| scripts. Any such change requires a new ADR and parser contract. | ||
|
|
||
| ## Consequences | ||
|
|
||
| - Search and the buyer popup retain the visible distinction between `m³` and | ||
| `m3` without exposing source HTML to the embedding model. | ||
| - Existing numeric-footnote tests remain unchanged because the bounded metric | ||
| pattern is the only new conversion. | ||
| - The current implementation does not claim to understand arbitrary equations; | ||
| unsupported script markup remains ordinary source text and must not be | ||
| presented as a parsed ontology expression. | ||
|
|
||
| ## References (APA 7th) | ||
|
|
||
| World Wide Web Consortium. (2026). *Mathematical Markup Language (MathML) | ||
| Version 4.0* (W3C Recommendation). https://www.w3.org/TR/mathml4/ |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -32,6 +32,27 @@ const NUMERIC_FOOTNOTE_MARKER = "\u0003lw-numeric-footnote\u0004"; | |
| const FOOTNOTE_BLOCK_MARKER = "\u0005lw-footnote-block\u0006"; | ||
| const FOOTNOTE_BLOCK_OPEN = /<\s*(?:footnote|endnote|w:footnote|w:endnote)\b[^>]*>/gi; | ||
| const NUMERIC_SUPERSCRIPT = /<sup\b[^>]*>\s*(\d{1,3})\s*<\/sup>/gi; | ||
| const SUPERSCRIPT_DIGITS = "⁰¹²³⁴⁵⁶⁷⁸⁹"; | ||
| const SUBSCRIPT_DIGITS = "₀₁₂₃₄₅₆₇₈₉"; | ||
| const METRIC_MARKUP = | ||
| /((?<![A-Za-z])(?:\d+(?:\.\d+)?\s*)?(?:km|cm|mm|kg|m))\s*<(sup|sub)\b[^>]*>\s*(\d{1,3})\s*<\/\2>/gi; | ||
|
Comment on lines
+37
to
+38
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 📝 Info: Lookbehind prevents footnote regression on words ending in a metric unit The Was this helpful? React with 👍 or 👎 to provide feedback. |
||
| const METRIC_PLAIN_SCRIPT = | ||
| /((?<![A-Za-z])(?:\d+(?:\.\d+)?\s*)?(?:km|cm|mm|kg|m))\s*(\^|_)\s*(?:\{(\d{1,3})\}|(\d{1,3}))/gi; | ||
|
|
||
| function normalizeMetricMarkup(raw: string): string { | ||
| return raw | ||
| .replace(METRIC_MARKUP, (_match, base: string, kind: string, digits: string) => { | ||
| const table = kind.toLowerCase() === "sup" ? SUPERSCRIPT_DIGITS : SUBSCRIPT_DIGITS; | ||
| return `${base}${[...digits].map((digit) => table[Number(digit)]).join("")}`; | ||
| }) | ||
| .replace( | ||
| METRIC_PLAIN_SCRIPT, | ||
| (_match, base: string, kind: string, bracedDigits: string, digits: string) => { | ||
| const table = kind === "^" ? SUPERSCRIPT_DIGITS : SUBSCRIPT_DIGITS; | ||
| return `${base}${[...(bracedDigits || digits)].map((digit) => table[Number(digit)]).join("")}`; | ||
| }, | ||
| ); | ||
| } | ||
|
Comment on lines
+42
to
+55
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 📝 Info: Opposite normalization order backend vs frontend is safe
Was this helpful? React with 👍 or 👎 to provide feedback. |
||
|
|
||
| function stripIndentMarkers(value: string): string { | ||
| return value | ||
|
|
@@ -224,7 +245,7 @@ function isDecodableBase64(raw: string): boolean { | |
|
|
||
| function pushText(segments: PostBodySegment[], raw: string, indentUnit: number): void { | ||
| const text = stripHtmlTags( | ||
| raw | ||
| normalizeMetricMarkup(raw) | ||
| .replace(FOOTNOTE_BLOCK_OPEN, FOOTNOTE_BLOCK_MARKER) | ||
| .replace(NUMERIC_SUPERSCRIPT, `${NUMERIC_FOOTNOTE_MARKER}$1`), | ||
| ); | ||
|
Comment on lines
246
to
251
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 📝 Info: Frontend normalizes plain scripts over raw HTML, backend over extracted text
Was this helpful? React with 👍 or 👎 to provide feedback. |
||
|
|
@@ -257,7 +278,7 @@ function markdownCells(line: string): string[] | null { | |
| const value = line.trim().replace(/^\|/, "").replace(/(?<!\\)\|$/, ""); | ||
| if (!value.includes("|")) return null; | ||
| const cells = value.split(/(?<!\\)\|/).map((cell) => cell.trim().replace(/\\\|/g, "|")); | ||
| return cells.length >= 2 && cells.every(Boolean) ? cells : null; | ||
| return cells.length >= 2 && cells.every(Boolean) ? cells.map(normalizeMetricMarkup) : null; | ||
| } | ||
|
|
||
| function isMarkdownSeparatorRow(cells: string[] | null): boolean { | ||
|
|
||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 📝 Info: Metric normalization applied before footnote detection keeps the two contracts disjoint In both paths, metric normalization runs strictly before superscript-footnote handling: Was this helpful? React with 👍 or 👎 to provide feedback.
devin-ai-integration[bot] marked this conversation as resolved.
|
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
📝 Info: Non-metric tags remain handled inconsistently (pre-existing)
In the frontend,
stripHtmlTagsexplicitly strips<sup>tags to empty string but not<sub>(postBodyDisplay.ts), so a non-metric subscript likex<sub>i</sub>is replaced with spaces (x i) rather than joined (xi). This asymmetry is pre-existing and not introduced by this PR — metric subs are converted to Unicode before stripping, so they are unaffected — but it is an inconsistency in how residual sup vs sub markup is collapsed that could matter for non-metric subscripts.Was this helpful? React with 👍 or 👎 to provide feedback.