Skip to content

Add guide lines feature#3644

Open
jsjgdh wants to merge 20 commits into
GraphiteEditor:masterfrom
jsjgdh:new_branch5
Open

Add guide lines feature#3644
jsjgdh wants to merge 20 commits into
GraphiteEditor:masterfrom
jsjgdh:new_branch5

Conversation

@jsjgdh

@jsjgdh jsjgdh commented Jan 16, 2026

Copy link
Copy Markdown
Contributor

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

@jsjgdh
jsjgdh marked this pull request as ready for review January 16, 2026 15:18
@jsjgdh jsjgdh changed the title Added guide lines feature with ruler drag interaction Add guide lines feature Jan 16, 2026
@jsjgdh
jsjgdh marked this pull request as draft January 16, 2026 16:46
@0HyperCube

0HyperCube commented Jan 17, 2026

Copy link
Copy Markdown
Contributor

It still looks strange when rotating a document with guides in:

rotating_document_with_guide.mp4

Also the snapping doesn't work when the document is rotated.

@0HyperCube 0HyperCube left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread editor/src/messages/portfolio/document/overlays/guide_overlays.rs Outdated
Comment thread editor/src/messages/portfolio/document/overlays/guide_overlays.rs Outdated
Comment thread editor/src/messages/portfolio/document/document_message_handler.rs Outdated
Comment thread editor/src/messages/portfolio/document/document_message.rs Outdated
Comment thread editor/src/messages/tool/tool_messages/select_tool.rs Outdated
Comment thread editor/src/messages/tool/tool_messages/select_tool.rs Outdated
Comment thread editor/src/messages/tool/tool_messages/select_tool.rs Outdated
@jsjgdh
jsjgdh marked this pull request as ready for review January 27, 2026 14:03
@jsjgdh
jsjgdh marked this pull request as draft January 27, 2026 14:18
@jsjgdh
jsjgdh marked this pull request as ready for review January 27, 2026 14:30
@Keavon

Keavon commented Feb 1, 2026

Copy link
Copy Markdown
Member

!build

@github-actions

github-actions Bot commented Feb 1, 2026

Copy link
Copy Markdown
📦 Build Complete for f2cb67b
https://c16f0eb0.graphite.pages.dev

@Keavon

Keavon commented Feb 1, 2026

Copy link
Copy Markdown
Member

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.

@0HyperCube

Copy link
Copy Markdown
Contributor

The snapping changes are from the object to the guide. For example when drawing a rectangle:
Snapping a rectangle to a guide intersection

I suppose it could also be useful to snap the origin of the guide when you are placing it (to match inkscape's features).

I'm not sure how you have determined that exactly 376 lines is the required number to implement this feature? However I guess I will defer to your superior knowledge on this matter.

Comment on lines +609 to +611
let transform = document
.navigation_handler
.calculate_offset_transform(viewport.center_in_viewport_space().into(), &document.document_ptz);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@Keavon
Keavon force-pushed the master branch 6 times, most recently from d6228da to e58c1de Compare March 16, 2026 23:03
@Keavon
Keavon force-pushed the master branch 5 times, most recently from 9b97ab7 to 2e842cb Compare March 19, 2026 11:00
@Keavon
Keavon force-pushed the master branch 2 times, most recently from f07c79b to 76938eb Compare April 29, 2026 12:16
@jsjgdh
jsjgdh marked this pull request as ready for review July 20, 2026 18:59
@jsjgdh

jsjgdh commented Jul 20, 2026

Copy link
Copy Markdown
Contributor Author

@cubic-dev

@cubic-dev-ai

cubic-dev-ai Bot commented Jul 20, 2026

Copy link
Copy Markdown
Contributor

@cubic-dev

@jsjgdh I have started the AI code review. It will take a few minutes to complete.

@jsjgdh

jsjgdh commented Jul 20, 2026

Copy link
Copy Markdown
Contributor Author

@cubic-dev

@cubic-dev-ai

cubic-dev-ai Bot commented Jul 20, 2026

Copy link
Copy Markdown
Contributor

@cubic-dev

@jsjgdh I have started the AI code review. It will take a few minutes to complete.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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>

Comment thread editor/src/messages/portfolio/document/guide_message_handler.rs
Comment thread frontend/src/components/widgets/inputs/RulerInput.svelte
Comment thread editor/src/messages/tool/common_functionality/snapping/guide_snapper.rs Outdated
Comment thread editor/src/messages/portfolio/document/guide_message_handler.rs
Comment thread frontend/src/components/panels/Document.svelte
Comment thread frontend/src/components/panels/Document.svelte Outdated
Comment thread frontend/wrapper/src/editor_commands.rs Outdated
- 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
@jsjgdh
jsjgdh force-pushed the new_branch5 branch 3 times, most recently from 58f72d2 to 60ebd10 Compare July 20, 2026 21:09

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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—include guide_lines in 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, and editor/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.svelte uses an isGuideEditor check 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);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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>
Suggested change
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 {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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>
Suggested change
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"
);

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.

Guide lines

3 participants