Skip to content

feat: sync upstream eigenpal/docx-js-editor main branch#9

Merged
yash-giantanalytics merged 32 commits intomainfrom
feat/sync-upstream-main
Apr 5, 2026
Merged

feat: sync upstream eigenpal/docx-js-editor main branch#9
yash-giantanalytics merged 32 commits intomainfrom
feat/sync-upstream-main

Conversation

@yash-giantanalytics
Copy link
Copy Markdown

Summary

  • Merges all upstream eigenpal/docx-js-editor main branch changes into our fork
  • Adds i18n support with LocaleProvider, useTranslation hook, and Polish/German translations (610 translatable keys)
  • Fixes tracked changes: spurious underline/strikethrough in suggest mode, consecutive deletion grouping, replacement tracked changes shown as single sidebar card
  • Cursor-based sidebar expansion for comments and tracked changes (replaces click-based, fixes max-update-depth crashes)
  • Unicode double-click word selection and curly apostrophe fix
  • Header/footer line spacing alignment with layout view
  • Scroll-based page indicator (Google Docs style)
  • All @eigenpal package references updated to @giantanalyticsai scope

Test plan

  • bun run typecheck passes for all packages
  • Lint-staged (eslint + prettier) passes
  • No @eigenpal references remain in runtime code (.ts, .tsx, .json)
  • All merge conflicts resolved preserving both our features (MentionProvider, collab, agent-use) and upstream additions (i18n, tracked change fixes)

jedrazb and others added 30 commits March 18, 2026 20:45
Add EditorBridge that connects AI agent tools to a live DocxEditor instance.
The agent can read the document, add comments, suggest tracked changes, and
scroll — all executing on the client without reloading the document.

Key changes:
- DocxEditorRef: 6 new methods (addComment, replyToComment, resolveComment,
  proposeReplacement, scrollToIndex, getComments)
- EditorBridge (packages/agent-use/bridge.ts): wraps DocxEditorRef for agent use
- Tool definitions (packages/agent-use/tools/): 6 tools with OpenAI-compatible
  schemas (read_document, read_comments, read_changes, add_comment,
  suggest_replacement, scroll_to)
- useAgentChat hook: wires tools to editor ref
- agent-chat-demo: Next.js example with chat panel beside the editor

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Replace getOpenAITools/getAnthropicTools with single getToolSchemas()
  (OpenAI format is the de facto standard)
- Simplify useAgentChat hook: returns { executeToolCall, toolSchemas }
  (removed stale bridge, getSystemContext, provider-specific helpers)
- Demo uses useAgentChat hook instead of manual bridge creation
- Fix multi-turn history: persistent openaiHistoryRef preserves tool
  call context across turns
- Fix bridge.getComments(): reads from live editor state, not stale
  Document model
- Fix replyToComment: validates parent comment exists
- Add 29 tests for tools + bridge (bun:test)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Extract getCommentText() helper, replacing inline type-cast chain
- Fix O(n^2) reply matching: pre-group with Map (same pattern as discovery.ts)
- Guard redundant editorRef.getComments() call when doc comments exist
- Memoize toolSchemas as module-level constant (static, never changes)
- Cache bridge creation via useMemo in useAgentChat hook
- Move msgId counter from module-level to useRef (scoped to component)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When `value` is undefined on first render, `stripExtension` returns
undefined, causing the input to start as uncontrolled. Adding a
fallback to empty string guarantees the input is controlled from
the first render, preventing the React warning.

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…enpal#228)

* fix: set correct default font-size on header/footer inline editor

The PM header editor's <p> elements used browser-default 16px font-size,
but text spans were rendered at the document's default (e.g. 11pt=14.67px).
This caused line-height: 1.15 to calculate based on 16px instead of 14.67px,
creating a visual spacing mismatch between the inline editor and the layout
painter view. Now resolves the default font size from document styles and
sets it on the editor container.

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

* fix: override inline spacing on header/footer editor paragraphs

The PM editor paragraphs got margin-bottom and line-height from
style-resolved spaceAfter/lineSpacing (e.g., Normal style), but the
layout painter doesn't apply style-resolved spacing to headers/footers.
This caused the editing view to show much more spacing than the layout
view. Use !important on margin and line-height in .hf-editor-pm to
force tight spacing matching the layout painter.

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

* fix: exclude spaceBefore/spaceAfter from header/footer height measurement

After editing, paragraph formatting can have style-resolved spaceAfter
(e.g., from Normal style) inlined via the PM→document round-trip. The
layout painter renders header/footer paragraphs without these margins,
but the measurement was including them — making the header area much
taller than the visible content. Now only lineSpacing is converted for
measurement, matching the tight rendering.

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

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…al#232)

* feat: show tracked change replacements as single "Replaced X with Y" card

When a deletion and insertion are adjacent with the same author and timestamp
(a Word replace operation), merge them into a single sidebar card showing
"Replaced <old> with <new>" instead of separate "Deleted" and "Added" entries.

Also extracts a reusable truncateText() helper for sidebar text truncation.

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

* fix: preserve comment range markers for tracked change replies on save

When replying to a tracked change, the reply comment was saved to
comments.xml but its range markers (commentRangeStart/End) were missing
from document.xml. This happened because the selective save system only
patches paragraphs tracked by ParagraphChangeTracker, but TC replies
only update React state (no PM transaction), so the affected paragraph
was never patched.

Fix: detect TC replies (parentId not in commentIdSet) alongside regular
comment replies when deciding whether to force full repack.

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

* fix: wrap both del+ins in comment range for TC reply markers

When a tracked change reply comment is anchored to a deletion that has
an adjacent insertion (replacement pair), wrap both elements inside the
comment range markers. Previously, only the deletion was wrapped, which
placed commentRangeEnd + commentReference between del and ins, breaking
the adjacency needed for replacement grouping in other editors.

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

* fix: thread comments on tracked changes as TC replies

Two fixes for comment-on-tracked-change interop:

1. toProseDoc: apply comment marks to text inside tracked change wrappers
   (insertion/deletion/moveFrom/moveTo). Previously, commentRangeStart/End
   were tracked but comment marks were only applied to regular runs, so
   comments overlapping tracked changes lost their mark in ProseMirror.

2. DocxEditor: detect comment marks overlapping tracked change marks and
   auto-set parentId to thread them as replies under the tracked change
   card in the sidebar.

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

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…pal#233)

* feat: add i18n support with locale prop and useTranslation hook (eigenpal#222)

Extract all ~485 user-facing strings into i18n/en.json and replace
hardcoded strings across 48 components with type-safe t() calls.

- Add LocaleProvider + useTranslation() hook (zero dependencies)
- Add `locale` prop on DocxEditor for partial overrides with deep merge
- Auto-derive LocaleStrings type from en.json (no manual interface)
- null values in locale files = "not yet translated" (falls back to English)
- Add scripts/validate-i18n.mjs for CI validation of locale file sync
- Add docs/i18n.md contribution guide
- Export getMissingTranslations() utility for translation coverage checks

Closes eigenpal#222

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

* chore: add i18n CLI (new/status), update docs, remove AGENTS_README

- Add `bun run i18n:new <lang>` to scaffold locale files with null keys
- Add `bun run i18n:status` to show translation coverage per locale
- Update docs/i18n.md with CLI reference and improved contribution flow
- Add i18n as prominent feature in README, link to contribution guide
- Remove AGENTS_README.md and its link from README
- Update CLAUDE.md with i18n CLI commands and "always use t()" rule

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

* fix: i18n review fixes — type safety, pluralization, memoization

- Fix DeepPartial to allow null values (community locale files)
- Remove all `as any` / `as Parameters<typeof t>[0]` casts by typing
  labelKey/nameKey fields as TranslationKey (14 files)
- Add tPlural() with CLDR plural rules (Slavic, Arabic, etc.)
- Add lang prop on DocxEditor for language-aware plural selection
- Auto-memoize locale prop via deep equality (no re-renders for inline objects)
- Fix validate-i18n.mjs: BCP 47 regex, empty parent cleanup, JSON error handling
- Add Limitations section to docs/i18n.md
- Convert replyCount/replyCountPlural to plural object format

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

* fix: use Intl.PluralRules instead of hand-rolled plural rules

Replace our incomplete hardcoded Slavic/Arabic plural rules with the
browser's built-in Intl.PluralRules API — the same standard used by
react-i18next and next-intl. Supports all languages correctly.

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

* refactor: use ICU MessageFormat for plurals instead of nested objects

Simpler approach: plurals are inline in the string value using ICU syntax
("{count, plural, one {# reply} other {# replies}}"). Same pattern as
next-intl. Keys stay identical across all languages — translators just
write their language's plural forms in the same string.

- Remove tPlural() — t() now handles ICU plural syntax natively
- Revert en.json replies to flat key with ICU format
- Revert validator to simple key matching (no plural object detection)
- Uses Intl.PluralRules for category selection

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

* feat: add ICU selectordinal and exact match (=N) support

Extend formatMessage to support the same ICU subset as next-intl:
- Exact matches: =0, =1, =2 (checked before CLDR categories)
- Cardinal plural: {count, plural, one {# item} other {# items}}
- Ordinal plural: {year, selectordinal, one {#st} two {#nd} other {#th}}

Add comprehensive Interpolation & Pluralization docs with examples.

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

* feat: i18n remaining ~200 strings, delete dead components

- Extract strings from ColorPicker, AdvancedColorPicker, FontSizePicker,
  TableOptionsDropdown subcomponents, TableMoreDropdown, TableToolbar,
  TableBorderColor/CellFill/BorderWidth pickers, HorizontalRuler,
  VerticalRuler, PrintPreview, LoadingIndicator, UnsavedIndicator,
  and dialog error messages (Hyperlink, InsertImage, PasteSpecial)
- Delete dead code: ShapeGallery.tsx, TableQuickActions.tsx (unused)
- Remove their en.json keys (shapes.*, tableQuickActions.*)
- Total: 610 translatable keys in en.json

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

* docs: remove partial locale override examples

The primary i18n path is importing a full locale file. Partial inline
overrides still work (deep merge) but don't need to be advertised.

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

* refactor: merge lang into locale via _lang key — single prop API

Remove the separate `lang` prop from DocxEditor. Language tag is now
embedded in the locale file as `"_lang": "de"`, set automatically by
`bun run i18n:new`. One prop to rule them all:

  <DocxEditor locale={de} />

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

* chore: simplify i18n — remove YAGNI code

- Remove getMissingTranslations() (zero callers, CLI does the same)
- Remove selectordinal support (unused in the editor)
- Remove localeEqual deep-compare memoization (consumers pass stable imports)
- LocaleContext.tsx: 200 → 137 lines

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

* refactor: replace locale + lang props with single i18n prop

API is now:
  <DocxEditor i18n={{ translations: de }} />
  <DocxEditor i18n={{ translations: pl, locale: 'pl' }} />

- locale field is optional (defaults to "en"), only needed for
  languages with complex plural rules (Slavic, Arabic)
- translations field is the JSON object (deep-merged with English)
- Remove _lang from en.json — no magic metadata in translation files
- Export I18nConfig type

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

* docs: explain why locale field matters for plural rules

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

* feat: type locale field with autocomplete for ~80 common BCP 47 tags

Locale type provides IDE autocomplete for common language codes while
still accepting any valid BCP 47 string.

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

* chore: start Locale type with just 'en', grow with new translations

Instead of listing ~80 language codes upfront, start with only 'en'
and expand the union as community translations are added. Docs updated
with step to add locale tag when contributing a new translation.

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

* refactor: simplify i18n API to single prop — <DocxEditor i18n={de} />

Locale JSON files now include _lang (set by i18n:new) for plural rules.
No wrapper object needed — just pass the imported JSON directly.

- Remove I18nConfig, Locale types (not needed)
- i18n prop is now just PartialLocaleStrings (the JSON object)
- _lang read from the locale object for Intl.PluralRules
- Update all docs to show simple <DocxEditor i18n={de} />

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

* refactor: rename i18n prop type to Translations

PartialLocaleStrings is a terrible consumer-facing name. Add a clean
Translations type alias. DocxEditor prop is now `i18n?: Translations`.

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

* fix: remove selectordinal docs (not implemented), drop redundant cast

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

* refactor: eliminate type casts with isRecord type predicate

Reduce casts from 8 to 3 (all at typed↔record boundaries):
- Add isRecord() type predicate — eliminates all internal casts
- deepMerge and getNestedValue are now cast-free internally
- Remove deepMerge generic (unnecessary, operates on AnyRecord)
- Zero `as any`, zero `as unknown`, zero double casts

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

* fix: treat _lang as metadata in validator, add _lang to pl.json

- Validator now skips _lang in key comparison (won't flag as missing/extra)
- Add _lang: "pl" to pl.json
- i18n:fix won't overwrite _lang with null
- i18n:status doesn't count _lang as untranslated

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

* chore: remove pl.json — let community contribute real translations

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

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Fix unicode double-click word selection

* Handle combining marks in word selection
The PR accidentally dropped ' (U+2019) from WORD_CHAR_REGEX, which would
break word selection for text with smart/typographic quotes.

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

Word assigns different revision IDs to the deletion and insertion parts
of a replacement tracked change. The sidebar card used only the deletion's
ID, so clicking the insertion part couldn't find the matching card.

Fix: store the insertion's revisionId in TrackedChangeEntry, build an
alias map, and use it in the click handler and active highlight CSS.

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

Two issues fixed:

1. Tracked change marks' toDOM used text-decoration CSS (underline for
   insertions, line-through for deletions). The underline and strike
   mark extensions have parseDOM rules matching those same CSS values,
   so ProseMirror's DOM parsing added real formatting marks on top of
   the tracked change visual styling — causing double underline on
   insertions and double strikethrough on deletions.

   Fix: remove text-decoration from tracked change toDOM styles. The
   hidden ProseMirror is off-screen; the layout-painter handles all
   visible tracked change rendering independently.

2. When typing inside existing tracked changes, two problems occurred:
   a) insertText() inherited deletion marks from cursor position,
      causing nested tracked changes with conflicting visual styles.
   b) removeMark(insertionType) + addMark() fragmented the insertion
      mark span, creating a separate tracked change for each keystroke.

   Fix: strip inherited deletion marks only. For insertion marks, let
   addMark() handle it — if the cursor is inside an existing insertion
   by the same author, the inherited mark stays intact and addMark()
   with matching attrs is a no-op that preserves the continuous span.

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add pl.json and de.json locale files with all 610 keys translated.
Terminology verified against official Microsoft Word PL/DE UI via
Microsoft Language Portal and support.microsoft.com documentation.

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…nges (eigenpal#244)

* chore: bump to 0.0.33

* refactor: use cursor-based mark detection for sidebar expansion

Replace the DOM click handler in UnifiedSidebar with ProseMirror
cursor-driven detection in DocxEditor. When the cursor lands on a
comment, insertion, or deletion mark, the matching sidebar card
expands automatically. This also works with keyboard navigation,
not just clicks.

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

* fix: reliable mark detection and eliminate sidebar state loop

Two fixes:

1. Mark detection: $from.marks() misses inclusive:false marks at
   cursor boundaries. Now checks nodeAfter/nodeBefore marks first
   for reliable comment and tracked change detection.

2. Sidebar crash: Remove bidirectional state sync (activeItemId →
   internal state → onExpandedItemChange → parent) that caused
   max update depth. Make sidebar fully controlled — parent owns
   expansion state, sidebar just reads activeItemId prop.

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

* fix: collect marks from all sources instead of OR chain

Empty mark arrays are truthy in JS, so the OR chain
(storedMarks || nodeAfter?.marks || nodeBefore?.marks)
short-circuited on the first source with any node, even if
that node had no comment/tracked-change marks. Now spreads
all sources into a single array so we always find the mark.

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

* fix: auto-open sidebar when clicking comment or tracked change

The sidebar items only exist in allSidebarItems when
showCommentsSidebar is true, but commentSidebarItems always
has them. Search commentSidebarItems for mark matching, and
auto-set showCommentsSidebar=true when a match is found so
clicking on a comment or tracked change opens the sidebar.

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

* refactor: unify insertion/deletion handling and stabilize toggleExpand

1. Merge near-duplicate insertion and deletion mark handling into
   a single block with shared prefix-search and alias-resolution logic.

2. Stabilize toggleExpand callback with a ref so it doesn't recreate
   on every expandedItem change, avoiding unnecessary card re-renders
   on each cursor move.

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

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The editor re-processed the entire document on every keystroke, causing
200-500ms input lag on 20+ page documents. This adds an incremental
pipeline that only re-converts, re-measures, and re-paginates from the
edited paragraph forward, with early exit when page breaks stabilize.

## Changes

**Incremental block conversion (Phase 1)**
- New `IncrementalBlockCache` tracks previous doc state and uses
  ProseMirror's structural sharing (node identity comparison) to detect
  which top-level nodes changed — O(1) per node
- Extracted `convertTopLevelNode()` from `toFlowBlocks()` for per-node
  incremental conversion
- List counter snapshots at each node boundary for correct numbering
  after partial re-conversion
- Forward propagation of list counter changes until stabilization

**Incremental measurement**
- `measureBlocksIncremental()` reuses cached measures for clean blocks
  before the dirty range, re-measures only from dirtyFrom forward
- Full floating zone pre-scan still runs (fast, zones can shift)

**Layout engine resume + early exit (Phase 2)**
- Paginator `snapshot()`/`createPaginatorFromSnapshot()` API for
  capturing and restoring layout state at page boundaries
- `layoutDocument()` accepts `resumeFrom` option to skip blocks before
  the dirty range and start from a saved paginator snapshot
- Early exit: after 2 consecutive blocks past the dirty range converge
  with the previous layout state, remaining pages are spliced from the
  previous run — avoids re-paginating the entire tail
- `applyContextualSpacingRange()` for partial spacing application

**CSS containment (Phase 3)**
- `content-visibility: auto` + `contain-intrinsic-size` on page shells
  so the browser skips layout/paint for off-screen pages

**Pipeline integration**
- PagedEditor detects doc changes via PM node identity comparison
  (works for both transaction-driven and direct relayout calls)
- Deferred cache mutation: `updateBlocks()` returns results without
  mutating the cache; `applyIncrementalResult()` is called only after
  successful paint to prevent split-state on stale aborts
- Per-step performance diagnostics via `console.debug`

## Measured Results (287 blocks, 24 pages)

| Step | Full pipeline | Incremental | Speedup |
|------|-------------|-------------|---------|
| Block conversion | 1.3ms | 0.0ms | ∞ |
| Measurement | 13.7ms | 0.5ms | **27x** |
| Layout | 0.5ms | 0.3ms (resumed) | 1.7x |
| Paint | 12.7ms | 12.7ms | 1x |
| **Total** | **~28ms** | **~13ms** | **2x** |

Steps 1-3 combined: **15ms → 0.8ms (19x faster)**

On larger documents (500+ blocks), the savings scale linearly since
unchanged blocks are completely skipped.

## Test Coverage

- 36 new unit tests (incrementalBlockCache, paginator-snapshot,
  layout-resume) — all passing
- 368/368 total unit tests passing
- Demo-docx E2E suite passing
- Typecheck clean across all 4 packages

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Internal planning document, not needed in the upstream repo.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
createView() already initializes EditorState from the document prop.
The document-change effect running on the same mount cycle would
create an identical state and call updateState(), which is redundant
and can destroy content injected by external plugins (e.g. ySyncPlugin)
between the two effects.

Set isInitializedRef in createView so the document-change effect
skips its first run when the document identity hasn't changed.
When collaborative=true, the mount-time useEffect that calls
loadDocument/loadDocumentBuffer is skipped. This allows external
ProseMirror plugins (e.g. ySyncPlugin from y-prosemirror) to manage
the document content lifecycle without the editor overwriting their
content on mount.

A document prop (e.g. createEmptyDocument()) is still required to
initialize the ProseMirror schema and editor shell.
- Extract getDocumentId helper to eliminate duplication between
  createView() and document-change effect (review comment #1)
- Fix docs example: add missing useMemo import, remove unused
  provider parameter (review comment #4)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
ySyncPlugin dispatches transactions during EditorView construction
(in its view() callback via _forceRerender). The arrow function
ignored ProseMirror's .call(this, tr) binding, and viewRef.current
was still null at that point, causing the transaction to be silently
dropped. This left ProseMirror with empty content even though Y.Doc
had real content.

Switch to a regular function so ProseMirror's this binding provides
the EditorView, and eagerly set viewRef.current on first dispatch.
Merge upstream changes including:
- i18n support with locale prop, useTranslation hook, and Polish/German translations
- Tracked changes fixes: spurious formatting, consecutive deletion grouping,
  replacement tracked changes as single sidebar card
- Cursor-based sidebar expansion for comments and tracked changes
- Unicode double-click word selection and curly apostrophe fix
- Header/footer editor line spacing alignment with layout view
- Scroll-based page indicator (Google Docs style)
- DocumentName controlled input fix

All @eigenpal package references updated to @giantanalyticsai scope.
…egration

Adds `collaborative` boolean prop to DocxEditor that skips mount-time
document loading when external plugins (e.g. y-prosemirror) manage
content. Prevents content corruption where mount effects would destroy
Yjs-populated state and sync the damage back to Y.Doc.

Also includes viewRef safety fix for transactions dispatched during
EditorView construction (before viewRef.current is assigned).
…ll splitting

Fixes right-click table row/column actions that were non-functional.
Adds merge cells and split cell to the context menu with a dialog-backed
split cell that accepts explicit row/column counts. Fixes column
insertion regression when adding a row then column from mid-table.
Adds incremental layout that reuses cached measurements for unchanged
blocks instead of re-measuring the entire document on every keystroke.
Uses ProseMirror structural sharing to detect dirty ranges, resumes
pagination from cached snapshots, and applies CSS containment for
browser-level optimization. ~30x faster measurement step on 20+ page
documents (13.7ms down to 0.5ms).
…tools

Adds EditorBridge that connects AI agent tools to the live DocxEditor
instance. Agents can read content, add comments, reply to comments,
propose tracked-change replacements, and scroll to positions — all
client-side. Includes useAgentChat hook, OpenAI-compatible tool
schemas, and a Next.js agent-chat-demo example app.
Lock file was stale after merging upstream PRs eigenpal#201, eigenpal#243, eigenpal#246, eigenpal#247.
Regenerated to include new dependencies (e.g. @happy-dom/global-registrator).
@yash-giantanalytics yash-giantanalytics merged commit 11e1196 into main Apr 5, 2026
2 checks passed
@yash-giantanalytics yash-giantanalytics deleted the feat/sync-upstream-main branch April 5, 2026 23:46
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.

5 participants