Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
63 commits
Select commit Hold shift + click to select a range
5df5c51
chore(webui): add canvas-harness deps
winlp4ever May 22, 2026
5e85dd9
chore(board): add canvas-harness conversion layer + tests
winlp4ever May 22, 2026
576b0d3
fix(board): swap label/content mapping in convert layer
winlp4ever May 22, 2026
1221866
chore(board): add canvas-harness theme adapter
winlp4ever May 22, 2026
bebbb1e
chore(board): add canvas-harness store + persistence layer
winlp4ever May 22, 2026
82023f5
fix(board): rename Dim0 node types to canvas-harness built-in names
winlp4ever May 22, 2026
d61b309
chore(board): add canvas-harness shared node views
winlp4ever May 22, 2026
dead742
chore(board): add folder custom node + central view router
winlp4ever May 22, 2026
7875c49
chore(board): add document custom node + preserve style.type in convert
winlp4ever May 22, 2026
b21b82d
chore(board): add widget custom node
winlp4ever May 22, 2026
19bef58
chore(board): add code-sandbox custom node
winlp4ever May 22, 2026
4be235e
chore(board): add sheet custom node — phase 3.2 complete
winlp4ever May 22, 2026
0b281f8
chore(board): mount canvas-harness behind feature flag
winlp4ever May 22, 2026
24a1dcc
chore(board): wire tool state + keyboard shortcuts
winlp4ever May 22, 2026
2e92645
chore(board): add minimal canvas chrome (toolbar / history / save sta…
winlp4ever May 22, 2026
9b13d9f
chore(board): wire drag-to-create + click-to-create for shape tools
winlp4ever May 22, 2026
3c72086
chore(board): drop feature flag — harness is now the default
winlp4ever May 22, 2026
d5c9f20
fix(board): bump harness chrome z-index above page header
winlp4ever May 22, 2026
2018d8c
fix(board): stop double-dividing opacity (was rendering at ~1% alpha)
winlp4ever May 22, 2026
d0e246e
chore(board): move harness undo/redo to bottom-left
winlp4ever May 22, 2026
7718b3d
fix(board): set view field on custom node defs so they mount via overlay
winlp4ever May 23, 2026
730449e
fix(board): disable autoFit on custom node types
winlp4ever May 23, 2026
1174fb1
fix(board): scrollable body layout for sheet + code-sandbox views
winlp4ever May 23, 2026
c7c1b06
fix(board): use Dim0 UUID scheme for canvas-harness ids
winlp4ever May 23, 2026
2f395f4
feat(board): wire per-board background tint + texture into harness theme
winlp4ever May 23, 2026
01fb85f
feat(board): add viewport-controls panel + tone down texture pattern
winlp4ever May 23, 2026
b41178d
feat(board): use --background CSS var as harness canvas default
winlp4ever May 23, 2026
6eaa0de
feat(board): per-board viewport persistence on harness canvas
winlp4ever May 23, 2026
e44dfc5
feat(board): add style panel for built-in node styling
winlp4ever May 23, 2026
cb0c014
feat(board): add folder / sheet / code-sandbox / widget tool buttons
winlp4ever May 23, 2026
4af60ad
feat(board): node surface dialog + editable titles for custom nodes
winlp4ever May 23, 2026
87f1bc5
feat(board): real editor panels + motion-aware inline views
winlp4ever May 23, 2026
d399d57
feat(board): bg-card canvas placeholders for custom nodes
winlp4ever May 23, 2026
d1c76cb
feat(board): folder navigation + parent_id on new nodes/edges
winlp4ever May 24, 2026
72c160e
fix(board): unblock title-caption clicks on custom nodes
winlp4ever May 24, 2026
50aebf4
feat(board): wire sheet surface to URL + subpage fetch + stack effect
winlp4ever May 24, 2026
1f4e7a9
feat(board): image drop, search dialog, center URL, image render fix
winlp4ever May 24, 2026
bc3bedd
feat(backend): add is_local_offset flag to PositionProperty
winlp4ever May 24, 2026
b917f9b
feat(board): persist edge endpoints as node-local offsets
winlp4ever May 24, 2026
1366dd9
fix(board): persist edge control point as on-curve midpoint
winlp4ever May 24, 2026
5462568
feat(board): sticky style memory across shape + arrow creations
winlp4ever May 24, 2026
2f2958a
feat(board): wire agent writes through harness store
winlp4ever May 24, 2026
fb0f33e
refactor(agent): read activeNodeSurface + rootId from harness store
winlp4ever May 24, 2026
47c075e
feat(agent): selection context reads from harness store
winlp4ever May 24, 2026
d5179e6
feat(board): right-click context menu + AI Spark on harness
winlp4ever May 24, 2026
4c15c71
feat(board): collapse AI section into expandable submenu
winlp4ever May 24, 2026
77c6371
feat(board): icon search + toolbar fidelity pass on harness
winlp4ever May 24, 2026
f122642
feat(board): dark-mode color projection on harness
winlp4ever May 25, 2026
e53f3b7
style(board): minimap uses --sidebar surface + --secondary-foreground…
winlp4ever May 25, 2026
cf9b833
feat(board): edge styling in StylePanel + sticky edge defaults
winlp4ever May 25, 2026
5839976
feat(board): thumbnail capture on harness via renderMinimapContent
winlp4ever May 25, 2026
926f939
feat(board): slides panel + presentation mode on harness
winlp4ever May 25, 2026
50e0413
feat(board): custom-node UX polish on harness
winlp4ever May 25, 2026
bdb79b5
fix(board): widget iframe renders on canvas; handwriting title font
winlp4ever May 25, 2026
866b099
chore(webui): bump canvas-harness to 0.1.0; split fast-refresh modules
winlp4ever May 25, 2026
32e3da2
fix(board): folder fill color + keep custom-node placeholders at low …
winlp4ever May 25, 2026
38ad495
feat(board): document upload on harness; nested-button hydration fix
winlp4ever May 25, 2026
4a3f2bd
feat(board): files + list views on harness
winlp4ever May 25, 2026
254355f
fix(backend,board): patch_note validates documents against Document m…
winlp4ever May 25, 2026
3c936a7
feat(markdown): render page refs + subpage blocks as chips
winlp4ever May 25, 2026
ce1e7b0
chore(board): phase 7 cutover — delete legacy react-flow surface
winlp4ever May 25, 2026
d16438c
chore(webui): bump canvas-harness to 0.1.2
winlp4ever May 25, 2026
07e8363
docs(readme): mention canvas-harness + board performance
winlp4ever May 25, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,10 @@ Everything on the board is a node:

https://github.com/user-attachments/assets/ad5de9f4-6f44-43a2-b59a-5279232d7f60

## Canvas engine

The board is built on [canvas-harness](https://github.com/winlp4ever/canvas-harness), a canvas-rendered node-graph library we maintain separately. Boards can hold thousands of nodes and still pan, zoom, and edit smoothly — comparable to tldraw and Excalidraw, and on par with hosted tools like Miro or FigJam.

## Agent layer

Built on the OpenAI Agents SDK, with board-aware tools wired in:
Expand Down
9 changes: 9 additions & 0 deletions backend/topix/datatypes/property.py
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,15 @@ class Position(BaseModel):

type: Literal[PropertyType.POSITION] = PropertyType.POSITION
position: Position | None = None
# When the position belongs to an edge endpoint AND the link's
# source/target resolves to an attached node, callers may interpret
# `position` as a node-local offset (relative to the node's
# top-left, pre-rotation) instead of an absolute world coordinate.
# Default `False` keeps the legacy world-coord interpretation for
# existing rows so no data migration is needed. Newer clients set
# this to True when saving attached endpoints so edges that move
# with their node don't require cascading updates.
is_local_offset: bool = False


class SizeProperty(Property):
Expand Down
13 changes: 11 additions & 2 deletions backend/topix/store/graph.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
MatchValue,
)

from topix.datatypes.file.document import Document
from topix.datatypes.graph.graph import Graph
from topix.datatypes.note.link import Link
from topix.datatypes.note.note import Note
Expand Down Expand Up @@ -122,7 +123,14 @@ def _deep_merge_dict(base: dict, patch: dict) -> dict:
return merged

async def patch_note(self, node_id: str, data: dict, user_uid: str | None = None) -> Note | None:
"""Patch a note by merging the update into the full stored note payload."""
"""Patch a note by merging the update into the full stored note payload.

Validates the merged payload against the matching pydantic model so
documents (subclass of Note with `type: Literal["document"]`) keep
their type discriminator and document-specific properties intact —
otherwise validating a document row against the bare `Note` model
fails with a literal_error on the `type` field.
"""
existing_nodes = await self.get_nodes([node_id])
if not existing_nodes:
return None
Expand All @@ -135,7 +143,8 @@ async def patch_note(self, node_id: str, data: dict, user_uid: str | None = None
data,
)
merged_payload["id"] = node_id
merged_note = Note.model_validate(merged_payload)
model = Document if isinstance(existing_note, Document) else Note
merged_note = model.model_validate(merged_payload)

await self._content_store.update([merged_note.model_dump(exclude_none=False)])
return merged_note
Expand Down
Loading
Loading