Skip to content

fix: keep write, erase, and undo atomic when input is interrupted - #3

Closed
DNTOF wants to merge 201 commits into
mainfrom
cursor/board-ink-loop-0e28
Closed

DNTOF wants to merge 201 commits into
mainfrom
cursor/board-ink-loop-0e28

Conversation

@DNTOF

@DNTOF DNTOF commented Nov 7, 2026 •

Copy link
Copy Markdown
Owner

做了什么

书写、局部擦除、撤销/重做在输入被打断时仍是一次完整操作。

  • pointercancel、仍按着时丢失捕获、窗口失焦、中途改工具,都会丢掉未完成的笔迹或擦除,不写入文档,也不留在纸上。
  • 橡皮擦按指针走过的路径命中,而不是只看采样点。半径是 12 CSS 像素再除以当前缩放;平移和缩放后的世界坐标与画面一致。设备像素比不参与换算。
  • 没有改动纸面的动作不进入撤销栈。撤销/重做同时恢复当时选中的公式。拖回起点或取消拖动会回到原位。
  • 手写识别默认关闭。抬笔不会识别;按 R 或点识别才读未标记笔迹。文字仍先过门控,公式才进本地 CoMER。

坐标、容差和撤销边界是按 MathScrap 自己的纸面语义定的,没有沿用其他白板的数值。

第二轮

复核上一轮时,合成 PointerEvent 没有走进浏览器自己的捕获和多指路径。用 Chrome 的可信输入再测:笔还按着时落下的触摸会把这次书写改成平移,抬笔后路径留在纸上,但文档里没有这笔。

现在这根触摸只被记住,不改当前手势,也不把点加进笔迹。笔抬起后这一笔照常提交。两指捏合仍会丢掉未完成的那一笔。鼠标在纸上按下、移到工具条上松开,笔画会提交,底下的按钮不会被按到。

长笔迹轮廓在这台机器的 Node 上量过:perfect-freehand 的 getStroke,800 点一笔、重复 30 帧,约 0.71 ms/帧;40 笔、每笔 120 点,约 0.62 ms/帧。这是算法时间,不是笔到屏幕的延迟,这一轮没有为它改绘制。

测试

先在 db43ad1 上看到掌触把笔迹吞掉并留幽灵路径,再修。5089d04 之后本地:

  • npm run smoke:ink
  • npm run e2e:ink(桌面 1440×900、手机 390×844、deviceScaleFactor 2)。笔和触摸走 Input.dispatchMouseEvent / Input.dispatchTouchEvent,鼠标松开走 Playwright 的真实鼠标,识别按钮会把 dataset.ocr 设为 run
  • npm run lint、npm run test:rust(135)、npm run smoke、npm run smoke:ocr、npm run smoke:ai、npm run test:pack
  • npm run e2e:sheet、npm run e2e(桌面 113/113,手机 14/14)
  • npm run pack:web

这些仍不是真机触控笔、掌触或原生设备。没有测量输入延迟或手感。上一轮 db43ad1 的绿色 CI 不覆盖这次提交。

范围

基于 main 6f1acd6c86fd429e5ad1f36309fe1948d97e60ed。不合并,不发 release,不删除分支。

Cursor and others added 30 commits October 30, 2026 12:40
Co-authored-by: allowcache <allowcache@gmail.com>
…eometry inference

Co-authored-by: allowcache <allowcache@gmail.com>
Co-authored-by: allowcache <allowcache@gmail.com>
…+ deduction panel

Co-authored-by: allowcache <allowcache@gmail.com>
…e space; add README

Co-authored-by: allowcache <allowcache@gmail.com>
Co-authored-by: allowcache <allowcache@gmail.com>
…ctor names

- Equations in x alone (x^2+2x+1=0) are solved (discriminant, roots, Vieta, vertex)
  and drawn as y = lhs - rhs with the roots marked, instead of an empty level-0 contour
- 2D windows frame zeros/extrema/intersections and cap the y-span; bounded implicit
  curves use their zero-set box; 3D z-range follows surface values
- Vectors are defined with bold names (**b**, 𝐛, \mathbf{b}, \vec{b}); a lone lowercase
  letter is never a vector definition or a point

Co-authored-by: allowcache <allowcache@gmail.com>
…formula type

Co-authored-by: allowcache <allowcache@gmail.com>
…lot tests

Co-authored-by: allowcache <allowcache@gmail.com>
Co-authored-by: allowcache <allowcache@gmail.com>
…dinate system

- Formulas are written straight onto the sheet (double-click, /, or the Σ tool) and render as ink; card tiles and washi tape are gone
- The corner 坐标系 button plots every formula on the page on the same axes, analysed together (intersections, shared unknowns); plane and space formulas get one shared system each
- Freeform-style chrome: one floating tool pill, a small undo/redo/… pill, blank paper by default
- Samples write one formula per line; smoke tests cover whole-sheet analysis

Co-authored-by: allowcache <allowcache@gmail.com>
Co-authored-by: allowcache <allowcache@gmail.com>
Co-authored-by: allowcache <allowcache@gmail.com>
- Remove the Chinese product name from the brand watermark, loading screen and README title
- Drop the brand watermark and the empty-page hint so a blank page is plain kraft paper
- Page title is MathScrap (was the scaffold default); the name sits at the top of the … menu
- e2e: assert no visible words on a blank sheet and no Chinese brand anywhere

Co-authored-by: allowcache <allowcache@gmail.com>
- circle.rs: solve sparse chord problems around one point P from a radius
  and three labeled segments — intersecting chords (相交弦), secants
  (割线), tangent+secant (切割线): the fourth segment, chord lengths,
  centre-to-chord distances, central angles, inscribed angles, sagitta,
  |OP| via power of a point, plus a canonical chord figure per case
  (P inside/outside auto-detected as two switchable cases when ambiguous)
- circles_analyze: cross inference between a circle and named points —
  powers, on/in/outside, tangent lengths, chords with centre distances,
  central/inscribed angles, automatic chord-crossing verification
- triangle_derived: midsegments (中位线) and angle bisector lengths with
  drawn feet (closes the README promise)
- vectors_analyze: projection vector proj_b a and perpendicular rest
- lines_analyze: parallel-pair transversals — corresponding/alternate/
  co-interior angles and the segment cut off between the parallels
- exports: circle_solve, circles_analyze, circle_of; WASM rebuilt
- statements: parse ⊙O: r = 5 / 圆O / circle O (radius, or through a
  point), 弦 AB、CD 交于 P, 割线 …, 切线 PT、割线 PAB, and the optional
  P 在圆内 / 在圆外 marker — LaTeX and kind labels included
- scene: resolve explicit circles (centre from point O or origin, radius
  from r or a through point), attach each chord statement to the nearest
  preceding circle (same convention as 过), render per-case sections with
  given facts, and cross-analyse sheet points against every circle —
  explicit ones and circles recognised among implicit curves via circle_of
- figure: draw ⊙O circles and the recovered chord figure (segments +
  labelled endpoints + P), slid onto the circle's own centre; window
  bounds account for both
…als, projections

- smoke cases: 相交弦定理 / 割线定理 / 切割线定理 (derived facts +
  min-fact counts), circle × points with powers and inscribed angles,
  parallel transversal angles, vector projection vector; smoke's 2D
  checker now verifies circle and circleCase ink inside the window
- new sample: 相交弦定理 written as two formulas on the sheet
- README: circle & chord syntax, deepened inference list, exports table,
  crate layout, case counts (66 formula + 17 sheet cases)
…tency checks, intercept theorem

- triangle_solve: HL rides SSA (verified); medians m_a as givens rebuild
  the opposite or adjacent side (m_a = ½√(2b²+2c²−a²) reversed); heights
  h_a with side a fix the area; two angles + area size the triangle via
  R = √(S/(2 sinA sinB sinC)); over-determined givens (extra angle or S)
  are checked against every solved case and rejected with the numbers
- triangle_derived: right triangles now add the altitude onto the
  hypotenuse and both leg projections (射影定理) with drawn feet
- lines_analyze: three ordered parallels cut by two transversals yield
  平行线分线段成比例 (both segment ratios, equality fact, drawn segments);
  two parallels + two transversals meeting at X yield the similar
  triangles statement with scale |XA|/|XC| = |XB|/|XD|
- derived_points_analyze: midpoints and section points (AP:PB = m:n) —
  coordinates, distances, ratio check, and midsegments between two
  midpoints sharing an endpoint (length + parallel fact)
- export: derived_points_analyze; WASM rebuilt
- statements: M = 中点(A, B) / midpoint, P = 分点(A, B, 1:2) (bare λ
  means AP:PB = λ:1), 𝐩 = 投影(𝐚, 𝐛) / proj, 𝐜 = 𝐚 ± 𝐛; triangle
  lines accept m_a / h_c keys (split lookahead widened); LaTeX + labels
  for the new kinds; triKeyLatex shared with the scene given-facts
- scene: definitions resolve in order into the same symbol tables, so
  constraints, circles, lines and vectors all see the derived objects;
  the points panel keeps the user-written polygon intact and renders
  derived points separately with the 中点与分点 fact section
…ints / intercept cases

- smoke: 9 new formula cases (b=4 from HL, a=5 from m_a, S=6 from h_c,
  equilateral from two angles + area, 射影定理 values, 中位线 MN,
  AP:PB = 1:2, derived vector sum + projection collinearity, intercept
  theorem + similarity) and 2 new sheet samples; counts now 77 + 19
- README: triangle knowns m_a/h_a, 中点/分点/投影 statement syntax,
  deepened parallel/vector sections, export table
…sive emergence

The pane is extracted from the modal so it can live docked beside the
sheet (next commit). While at it, the refresh semantics are rebuilt:

- analysis input goes through useDeferredValue: heavy Rust inference
  never blocks writing on the sheet
- the user's zoom survives every sheet edit (the old objectKey change
  force-reset the view on each new formula); a 适应视野 button refits,
  and plane/space switching still refits
- PlotView purges only on plane/space change, not on every object set
  change
- progressive figure: statements emerge one by one — derived during
  render (StrictMode-safe), steps gated on the first actual draw so the
  growth is visible instead of finishing inside the plotly loading gap;
  honours prefers-reduced-motion and skips via ?static=1 (used by e2e)
- PlotView gains onReady (first successful draw)
The corner 坐标系 button now toggles a docked pane (42% width, md+) that
sits parallel to the paper: write with the composer or the pen while the
shared axes update live. The pane header offers 全屏 (the previous
modal, unchanged on phones) and 适应视野; Escape closes the dock.

- App layout becomes a flex row: relative sheet pane + docked aside;
  TopBar / Toolbar / Composer / MathPalette / PlotButton switch from
  fixed to absolute so all chrome anchors to the paper side
- sheet size measured with a ResizeObserver: writing spots, fit-view
  and zoom now follow the narrowed paper instead of the window
- fitView fixes a pre-existing y-anchoring bug (used x0 for the y offset)
- G toggles the dock; Enter on a selection opens it focused
…ult on)

- clusterStrokes: settled ink groups into one cluster per formula
  (greedy bounding-box merge), so chips appear per written formula
- InkChip: a kraft pill floats just below the ink with the best
  candidate (LaTeX + confidence); ✓ writes it onto the sheet and into
  the live plot, ⟳ cycles candidates, ✕ ignores the strokes for good
- the pipeline runs ~900ms after the last stroke while auto-OCR is on
  (default), pauses while the composer or the manual dialog is open,
  and re-chips when new strokes join a cluster
- settings: 手写自动识别 toggle (default on); R still opens the manual
  recognition dialog on recent strokes
- e2e:sheet gains a dockAndLive pass: the corner button docks the pane
  beside the paper, writing via the composer updates the shared axes
  live, wheel zoom persists across further writes, the figure emerges
  statement by statement (0 → 1 → 2 → 3 → 4 traces, reducedMotion
  forced on), and settled pen strokes raise an automatic recognition
  chip whose ✓ writes the formula onto the sheet
- both e2e scripts default to ?static=1 so trace assertions stay
  deterministic; the reveal pass loads the raw URL
- README: docked side-by-side pane, 边写边画 / zoom persistence /
  progressive emergence, the automatic recognition pipeline
Settings can test an OpenAI-compatible, Anthropic, or local endpoint.
A sheet chat applies the same in-page tools, and a stdio MCP server
forwards calls to the open Vite app. API keys stay in localStorage.

Co-authored-by: DNT_OF <allowcache@gmail.com>
The confirm chip scored only aspect ratio, so a wide y = x matched
a = (1, 2, 2) at a fixed 62%. Geometry now reads simple equations
and an arrow over a letter, and drops component tuples unless
parentheses were actually drawn.

Co-authored-by: DNT_OF <allowcache@gmail.com>
A small 2 on the shoulder of a letter was merged into the letter, the
match failed, and unrecognized ink was shown as y = x at 24%. Powers
are split out, and a miss now says it was not recognized.

Co-authored-by: DNT_OF <allowcache@gmail.com>
DNTOF and others added 26 commits November 2, 2026 19:28
Land X(381)–X(390): midpoint(G,H) orthocentroidal center X381,
reflection of O in H (X382), Euler∩X14X98 (X383; skip isos),
Conway Euler X32X76 (X384), X2-Hirst of K (X385; skip equil),
Brocard-inverse of X58 (X386), X1X2∩X4X6 (X387), X1X4∩X7X8 (X388),
Taylor-circle center (X389), and reflection of Ge in I (X390).
Land X(391)–X(400): X2X6∩X8X9 (X391), X1X6∩X10X11 (X392),
tan²A X25-cross of H (X393; skip right), cos²A X69-Ceva of O (X394),
mid(X14,X16)/mid(X13,X15) (X395/X396; X395 equil→G),
crosspoint(H,X17/X18) (X397/X398), Parry reflection (X399),
and Yff–Malfatti sin A csc⁴(A/4) (X400).
Land X(401)–X(410) barycentrics: Bailey Hirst, Zeeman–Gossard p·y,
orthic-X36 mid(H,X186), Schiffler Euler harmonic, X1X6/X10X33 Euler
intercepts, X4×X225 crosspoint, X73X228 Euler cut, and X21/X29
1st-substitutions. Skip equil for X401–X403; skip right for tan/sec
centers X403/X406–X408/X410. Unit test on 6-7-8 / isos / equil / 3-4-5.
Land X(411)–X(420) barycentrics: X21/X29 2nd·3rd Euler substitutions,
X4-Hirst of X29 (via X21), X3-Hirst of X21 (via X29), X3-Ceva of
X185/X216, and X4-Hirst of X25/X427 (via X31/X38). Skip equil for
vanishing/Hirst weights; skip right for X29/sec/csc/tan centers.
Unit test on 6-7-8 / isos / equil / 3-4-5.
Land X(421)–X(430) barycentrics: X4-Hirst of X24/X28/X27/X451/X21
(via X47/X58/X81/X191/X283), Euler X19-4th-subst, complement of
X22, and Euler X38/X58/X81-5th-subst. Skip equil for vanishing
Hirst weights; skip right for tan/sec centers.
Unit test on 6-7-8 / isos / equil / 3-4-5.
Land closed barycentrics for X(431)–X(440) on the Euler-line band:
X283-5th, X155/X159/X195/X399-6th, X48-7th, X214-8th, X204-9th,
X193-10th, and complement of X(27). Guards: skip right for tan/sec
forms; skip equilateral for X437 (X214 tril vanishes).
Land closed barycentrics for X(441)–X(450): complement of X(297)/X(21)/X(452),
Euler∩X(19)X(232), Euler X(79)-11th-subst, crosspoint X(98)·X(511),
X(2)-Hirst of X(27)/X(21)/X(452), and X(3)-Hirst of X(4). Guards: skip right
for tan/sec forms; skip equilateral for ω/Hirst-vanishing centers.
Bundle the existing sheet, CoMER ONNX, and mathcore WASM in a Tauri shell that does not call the Vite server. Android debug APKs are arm64 and debug-signed; Windows and iOS builds stay on the machines that can sign them.

Co-authored-by: DNT_OF <allowcache@gmail.com>
The copy step was also picking up Cargo build-script executables from target/release.

Co-authored-by: DNT_OF <allowcache@gmail.com>
Explicit-base logarithms keep ln natural and unary log/lg as log10.
The sheet UI follows the browser locale with a saved compact switch.
README pairs each Chinese paragraph with its English translation.
GitHub Actions runs lint, Rust, smoke, OCR, AI, pack checks, and the sheet e2e.

Co-authored-by: DNT_OF <allowcache@gmail.com>
The production check rejected every installer because the page bundle
contained the local dev-server address. That address now stays in the
dev-only forwarder, which the production build does not import.

English mode translates geometry headings, groups, and listed reasons
on screen. Formulas, point names, and the engine payload stay as emitted.
The plot end-to-end run is part of the Actions test job, and its pixel
check counts dark ink on the kraft sheet.

Co-authored-by: DNT_OF <allowcache@gmail.com>
…and CI

Integrate the tested MathScrap packaging branch into main, preserving branch history.
Keep main's geometry, WASM, logarithms, bilingual UI, packaging, and CI.
The merge had inserted a second copy of the X(91)/X(92) block already on
main; that duplicate is removed so each center is emitted once.
Keep the OCR fixes that main did not have: an isolated sigma misread as
0, Y, or u is rewritten only when the ink matches, and summation bars
are not treated as an over-arrow.

Vite resolves the source alias with import.meta.dirname.
Pushes to main run the same test job; installer jobs still run only
after that job, and only on push.

Co-authored-by: DNT_OF <allowcache@gmail.com>
Preserve both branch histories, integrate sigma and summation recognition fixes, and include main push CI, ESM directory resolution and README command corrections.
A cancelled, blurred, or tool-switched stroke is dropped instead of committed. The eraser follows the pointer path in world units after zoom and pan. Undo restores the formula selection, and a gesture that does not move the sheet is not a history step. Handwriting recognition starts only from an explicit click.

Co-authored-by: DNT_OF <allowcache@gmail.com>
A touch that arrives while the pen is down no longer replaces the stroke with a pan or leaves the ink on the paper without committing it. Extra contacts are captured so their release is observed, and only the active pointer extends or erases the stroke. Two-finger pinch still drops an unfinished stroke.

Co-authored-by: DNT_OF <allowcache@gmail.com>
@cursor cursor Bot closed this Nov 7, 2026
@cursor
cursor Bot deleted the cursor/board-ink-loop-0e28 branch November 7, 2026 14:35
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.

2 participants