Add guide lines feature#3644
Conversation
|
It still looks strange when rotating a document with guides in: rotating_document_with_guide.mp4Also the snapping doesn't work when the document is rotated. |
0HyperCube
left a comment
There was a problem hiding this comment.
Seems to work well; I have a few suggestions for the code structure. I won't comment on the JS changes.
Please mark the PR as «Ready to review» when you are happy with it.
|
!build |
|
|
Some of the ~750 lines of code is for snapping, right? At least when I glanced over it last, I thought I saw that. In testing this build link, I am not seeing any snapping to objects in the document. The "Move Guide" hint should be removed since the user is already dragging the guide. More review to follow when I have time. My previously stated concern that the line count seems way too high still applies and I'm hesitant to merge it before that can be either resolved or justified to me as to why it's about double the number of lines I would expect from this feature. |
| let transform = document | ||
| .navigation_handler | ||
| .calculate_offset_transform(viewport.center_in_viewport_space().into(), &document.document_ptz); |
There was a problem hiding this comment.
Please use document.metadata().transform_to_viewport directly. There should be no need to use the calculate_offset_transform anywhere in your code since it is already cached.
d6228da to
e58c1de
Compare
9b97ab7 to
2e842cb
Compare
f07c79b to
76938eb
Compare
Co-authored-by: James Lindsay <78500760+0HyperCube@users.noreply.github.com>
Co-authored-by: James Lindsay <78500760+0HyperCube@users.noreply.github.com>
|
@jsjgdh I have started the AI code review. It will take a few minutes to complete. |
|
@jsjgdh I have started the AI code review. It will take a few minutes to complete. |
There was a problem hiding this comment.
1 issue found across 20 files
Confidence score: 3/5
- In
editor/src/messages/portfolio/document/document_message_handler.rs, guide-only edits can be treated as already saved because dirty tracking hashes only the node network, so autosave may skip real changes and users can lose guide edits if they close or crash before a manual save — include guide state in the document dirty hash (or explicitly invalidate dirty state on guide changes) before merging.
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="editor/src/messages/portfolio/document/document_message_handler.rs">
<violation number="1" location="editor/src/messages/portfolio/document/document_message_handler.rs:90">
P1: Guide-only edits remain marked saved and are skipped by autosave because dirty tracking hashes only the node network, not this serialized handler. Include guide state in the document dirty hash, or explicitly invalidate save/autosave state for guide mutations.</violation>
</file>
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
| #[serde(skip)] | ||
| pub data_panel_message_handler: DataPanelMessageHandler, | ||
| #[serde(flatten)] | ||
| pub guide_lines_message_handler: GuideLinesMessageHandler, |
There was a problem hiding this comment.
P1: Guide-only edits remain marked saved and are skipped by autosave because dirty tracking hashes only the node network, not this serialized handler. Include guide state in the document dirty hash, or explicitly invalidate save/autosave state for guide mutations.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At editor/src/messages/portfolio/document/document_message_handler.rs, line 90:
<comment>Guide-only edits remain marked saved and are skipped by autosave because dirty tracking hashes only the node network, not this serialized handler. Include guide state in the document dirty hash, or explicitly invalidate save/autosave state for guide mutations.</comment>
<file context>
@@ -85,6 +86,8 @@ pub struct DocumentMessageHandler {
#[serde(skip)]
pub data_panel_message_handler: DataPanelMessageHandler,
+ #[serde(flatten)]
+ pub guide_lines_message_handler: GuideLinesMessageHandler,
// ============================================
</file context>
- Fix isInRulerArea to check all four viewport edges (not just one) - Add pointercancel handler to abort guide drag on cancelled interactions - Guard hit_test with guide_lines_visible so hidden guides don't capture hover/drag - Gate intersection snap on guide_lines_visible to match get_snap_lines() behaviour - Validate direction string in create_guide_line; panic on invalid values instead of silently defaulting to vertical
58f72d2 to
60ebd10
Compare
There was a problem hiding this comment.
7 issues found across 20 files
Confidence score: 2/5
- In
editor/src/messages/portfolio/document/guide_message_handler.rs, guide-only edits are currently outside the saved-state/snapshot flow, so users can change guides without getting unsaved/autosave protection and may lose those edits unexpectedly—includeguide_linesin the saved-state/hash path before merging. - Guide drag behavior has multiple concrete interaction regressions across
frontend/src/components/panels/Document.svelte,frontend/src/components/widgets/inputs/RulerInput.svelte, andeditor/src/messages/portfolio/document/guide_message_handler.rs: ruler direction is inverted, drags can fail when selection overlays intercept input, Escape cannot restore the start position, and unmount during drag can leave handlers/provisional guides behind—fix orientation mapping and centralize drag lifecycle cleanup (including abort/onDestroy restore/delete paths) before merge. - In
editor/src/messages/tool/common_functionality/snapping/guide_snapper.rs, snapping work can degrade to quadratic per pointer update as guide count grows, which risks visible lag while dragging in guide-heavy documents—switch to nearest horizontal/vertical guide selection and emit only their intersection. frontend/src/components/panels/Document.svelteuses anisGuideEditorcheck that does not actually validate capabilities, so unsupported editor objects fail later with a less actionable runtime error—replace it with explicit method checks (or rely on the generated wrapper type) to fail early and clearly.
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="editor/src/messages/tool/common_functionality/snapping/guide_snapper.rs">
<violation number="1" location="editor/src/messages/tool/common_functionality/snapping/guide_snapper.rs:77">
P2: Snapping can become quadratic in the number of guide lines on each pointer update. Find the closest horizontal and vertical guide independently and emit their single intersection instead of passing all guides to the pairwise helper.</violation>
</file>
<file name="frontend/src/components/panels/Document.svelte">
<violation number="1" location="frontend/src/components/panels/Document.svelte:176">
P3: `isGuideEditor` is a no-op type check, so unsupported editor objects fail later with an opaque `TypeError`. Check the three methods (or remove the misleading guard and use the generated wrapper type).</violation>
<violation number="2" location="frontend/src/components/panels/Document.svelte:248">
P2: Navigating away or closing the document during a guide drag leaves global handlers active and leaves the provisional guide behind. Retain the active drag cleanup and invoke it from `onDestroy` (deleting a newly created guide as cancellation requires).</violation>
<violation number="3" location="frontend/src/components/panels/Document.svelte:266">
P2: Dragging from the top ruler creates a horizontal guide instead of the expected vertical guide; the left ruler has the inverse result. Convert the ruler orientation to the perpendicular guide direction before `createGuideLine`.</violation>
</file>
<file name="editor/src/messages/portfolio/document/guide_message_handler.rs">
<violation number="1" location="editor/src/messages/portfolio/document/guide_message_handler.rs:82">
P1: Guide-only edits are not marked unsaved or autosaved because `guide_lines` is outside the network hash and this mutation does not commit document storage. Include guide state in the saved-state/snapshot path (and make guide edits transactional) so creating, moving, deleting, or toggling guides persists reliably.</violation>
<violation number="2" location="editor/src/messages/portfolio/document/guide_message_handler.rs:90">
P2: Canceling a guide drag leaves the guide at its last pointer-move position; Escape cannot restore its recorded starting position. Restore that position on abort, or defer this persisted mutation until drag completion.</violation>
</file>
<file name="frontend/src/components/widgets/inputs/RulerInput.svelte">
<violation number="1" location="frontend/src/components/widgets/inputs/RulerInput.svelte:254">
P2: Dragging from a ruler fails while a selection is visible because the selection overlay sits above `.ruler-area` and this handler never receives `pointerdown`. Make the overlay transparent to pointer input or attach the drag-start handler to a common ancestor.</violation>
</file>
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
| }; | ||
|
|
||
| let guide_line = GuideLine::with_id(id, direction, document_position); | ||
| self.guide_lines.push(guide_line); |
There was a problem hiding this comment.
P1: Guide-only edits are not marked unsaved or autosaved because guide_lines is outside the network hash and this mutation does not commit document storage. Include guide state in the saved-state/snapshot path (and make guide edits transactional) so creating, moving, deleting, or toggling guides persists reliably.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At editor/src/messages/portfolio/document/guide_message_handler.rs, line 82:
<comment>Guide-only edits are not marked unsaved or autosaved because `guide_lines` is outside the network hash and this mutation does not commit document storage. Include guide state in the saved-state/snapshot path (and make guide edits transactional) so creating, moving, deleting, or toggling guides persists reliably.</comment>
<file context>
@@ -0,0 +1,121 @@
+ };
+
+ let guide_line = GuideLine::with_id(id, direction, document_position);
+ self.guide_lines.push(guide_line);
+ responses.add(OverlaysMessage::Draw);
+ responses.add(PortfolioMessage::UpdateDocumentWidgets);
</file context>
| <div | ||
| class={`ruler-area ${direction === "Horizontal" ? "horizontal" : "vertical"}`} | ||
| bind:this={rulerInput} | ||
| on:pointerdown={handlePointerDown} |
There was a problem hiding this comment.
P2: Dragging from a ruler fails while a selection is visible because the selection overlay sits above .ruler-area and this handler never receives pointerdown. Make the overlay transparent to pointer input or attach the drag-start handler to a common ancestor.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At frontend/src/components/widgets/inputs/RulerInput.svelte, line 254:
<comment>Dragging from a ruler fails while a selection is visible because the selection overlay sits above `.ruler-area` and this handler never receives `pointerdown`. Make the overlay transparent to pointer input or attach the drag-start handler to a common ancestor.</comment>
<file context>
@@ -198,11 +204,58 @@
+ <div
+ class={`ruler-area ${direction === "Horizontal" ? "horizontal" : "vertical"}`}
+ bind:this={rulerInput}
+ on:pointerdown={handlePointerDown}
+ on:pointerup={handlePointerUp}
+ on:pointercancel={handlePointerCancel}
</file context>
| const handlers = createGuideDragHandlers(options); | ||
| if (!handlers) return; | ||
|
|
||
| window.addEventListener("pointermove", handlers.onMove); |
There was a problem hiding this comment.
P2: Navigating away or closing the document during a guide drag leaves global handlers active and leaves the provisional guide behind. Retain the active drag cleanup and invoke it from onDestroy (deleting a newly created guide as cancellation requires).
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At frontend/src/components/panels/Document.svelte, line 248:
<comment>Navigating away or closing the document during a guide drag leaves global handlers active and leaves the provisional guide behind. Retain the active drag cleanup and invoke it from `onDestroy` (deleting a newly created guide as cancellation requires).</comment>
<file context>
@@ -160,6 +164,109 @@
+ const handlers = createGuideDragHandlers(options);
+ if (!handlers) return;
+
+ window.addEventListener("pointermove", handlers.onMove);
+ window.addEventListener("pointerup", handlers.onRelease);
+ window.addEventListener("pointercancel", handlers.onCancel);
</file context>
| draggingGuideId = guideId; | ||
| draggingGuideDirection = direction; | ||
|
|
||
| getEditorHandle().createGuideLine(guideId, direction, mouseX, mouseY); |
There was a problem hiding this comment.
P2: Dragging from the top ruler creates a horizontal guide instead of the expected vertical guide; the left ruler has the inverse result. Convert the ruler orientation to the perpendicular guide direction before createGuideLine.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At frontend/src/components/panels/Document.svelte, line 266:
<comment>Dragging from the top ruler creates a horizontal guide instead of the expected vertical guide; the left ruler has the inverse result. Convert the ruler orientation to the perpendicular guide direction before `createGuideLine`.</comment>
<file context>
@@ -160,6 +164,109 @@
+ draggingGuideId = guideId;
+ draggingGuideDirection = direction;
+
+ getEditorHandle().createGuideLine(guideId, direction, mouseX, mouseY);
+ startGuideDrag({ deleteOnCancel: true });
+ }
</file context>
| getEditorHandle().createGuideLine(guideId, direction, mouseX, mouseY); | |
| getEditorHandle().createGuideLine(guideId, direction === "Horizontal" ? "Vertical" : "Horizontal", mouseX, mouseY); |
| let document_point = document_point(mouse_x, mouse_y); | ||
|
|
||
| if let Some(guide_line) = self.guide_lines.iter_mut().find(|guide_line| guide_line.id == id) { | ||
| guide_line.position = match guide_line.direction { |
There was a problem hiding this comment.
P2: Canceling a guide drag leaves the guide at its last pointer-move position; Escape cannot restore its recorded starting position. Restore that position on abort, or defer this persisted mutation until drag completion.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At editor/src/messages/portfolio/document/guide_message_handler.rs, line 90:
<comment>Canceling a guide drag leaves the guide at its last pointer-move position; Escape cannot restore its recorded starting position. Restore that position on abort, or defer this persisted mutation until drag completion.</comment>
<file context>
@@ -0,0 +1,121 @@
+ let document_point = document_point(mouse_x, mouse_y);
+
+ if let Some(guide_line) = self.guide_lines.iter_mut().find(|guide_line| guide_line.id == id) {
+ guide_line.position = match guide_line.direction {
+ GuideLineDirection::Horizontal => document_point.y,
+ GuideLineDirection::Vertical => document_point.x,
</file context>
| }; | ||
|
|
||
| function isGuideEditor(x: unknown): x is GuideEditor { | ||
| return true; |
There was a problem hiding this comment.
P3: isGuideEditor is a no-op type check, so unsupported editor objects fail later with an opaque TypeError. Check the three methods (or remove the misleading guard and use the generated wrapper type).
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At frontend/src/components/panels/Document.svelte, line 176:
<comment>`isGuideEditor` is a no-op type check, so unsupported editor objects fail later with an opaque `TypeError`. Check the three methods (or remove the misleading guard and use the generated wrapper type).</comment>
<file context>
@@ -160,6 +164,109 @@
+ };
+
+ function isGuideEditor(x: unknown): x is GuideEditor {
+ return true;
+ }
+
</file context>
| return true; | |
| return ( | |
| typeof x === "object" && | |
| x !== null && | |
| "createGuideLine" in x && | |
| typeof x.createGuideLine === "function" && | |
| "moveGuideLine" in x && | |
| typeof x.moveGuideLine === "function" && | |
| "deleteGuideLine" in x && | |
| typeof x.deleteGuideLine === "function" | |
| ); |

Adds feature for guide lines that can be dragged from the rulers to help with alignment .closes #2601

image of guide lines: