From b0e1b696bc8a07d8e3c924c406abc9e8fed34980 Mon Sep 17 00:00:00 2001 From: Kulratan Thapar Date: Tue, 3 Mar 2026 11:11:47 +0000 Subject: [PATCH 1/5] Add support for tilted rulers --- .../src/messages/frontend/frontend_message.rs | 1 + .../document/document_message_handler.rs | 1 + .../src/components/panels/Document.svelte | 28 +++- .../widgets/inputs/RulerInput.svelte | 125 ++++++++++++------ frontend/src/messages.ts | 2 + 5 files changed, 113 insertions(+), 44 deletions(-) diff --git a/editor/src/messages/frontend/frontend_message.rs b/editor/src/messages/frontend/frontend_message.rs index 2160834136..4a5654fd4e 100644 --- a/editor/src/messages/frontend/frontend_message.rs +++ b/editor/src/messages/frontend/frontend_message.rs @@ -248,6 +248,7 @@ pub enum FrontendMessage { spacing: f64, interval: f64, visible: bool, + tilt: f64, }, UpdateDocumentScrollbars { position: (f64, f64), diff --git a/editor/src/messages/portfolio/document/document_message_handler.rs b/editor/src/messages/portfolio/document/document_message_handler.rs index 76fbfa4828..c24d3684d5 100644 --- a/editor/src/messages/portfolio/document/document_message_handler.rs +++ b/editor/src/messages/portfolio/document/document_message_handler.rs @@ -807,6 +807,7 @@ impl MessageHandler> for DocumentMes spacing: ruler_spacing, interval: ruler_interval, visible: self.rulers_visible, + tilt: if self.graph_view_overlay_open { 0. } else { current_ptz.tilt() }, }); } DocumentMessage::RenderScrollbars => { diff --git a/frontend/src/components/panels/Document.svelte b/frontend/src/components/panels/Document.svelte index d8453997f8..23e90690c2 100644 --- a/frontend/src/components/panels/Document.svelte +++ b/frontend/src/components/panels/Document.svelte @@ -60,6 +60,7 @@ let rulerSpacing = 100; let rulerInterval = 100; let rulersVisible = true; + let rulerTilt = 0; // Rendered SVG viewport data let artworkSvg = ""; @@ -296,11 +297,12 @@ scrollbarMultiplier = multiplier; } - export function updateDocumentRulers(origin: XY, spacing: number, interval: number, visible: boolean) { + export function updateDocumentRulers(origin: XY, spacing: number, interval: number, visible: boolean, tilt: number) { rulerOrigin = origin; rulerSpacing = spacing; rulerInterval = interval; rulersVisible = visible; + rulerTilt = tilt; } // Update mouse cursor icon @@ -479,8 +481,8 @@ editor.subscriptions.subscribeJsMessage(UpdateDocumentRulers, async (data) => { await tick(); - const { origin, spacing, interval, visible } = data; - updateDocumentRulers(origin, spacing, interval, visible); + const { origin, spacing, interval, visible, tilt } = data; + updateDocumentRulers(origin, spacing, interval, visible, tilt); }); // Update mouse cursor icon @@ -572,13 +574,29 @@ {#if rulersVisible} - + {/if} {#if rulersVisible} - + {/if} diff --git a/frontend/src/components/widgets/inputs/RulerInput.svelte b/frontend/src/components/widgets/inputs/RulerInput.svelte index 1c907bcbd3..8a0a31c1b2 100644 --- a/frontend/src/components/widgets/inputs/RulerInput.svelte +++ b/frontend/src/components/widgets/inputs/RulerInput.svelte @@ -11,81 +11,129 @@ const MICRO_MARK_THICKNESS = 3; export let direction: RulerDirection = "Vertical"; - export let origin: number; + export let originX: number; + export let originY: number; export let numberInterval: number; export let majorMarkSpacing: number; export let minorDivisions = 5; export let microDivisions = 2; + export let tilt: number = 0; let rulerInput: HTMLDivElement | undefined; let rulerLength = 0; let svgBounds = { width: "0px", height: "0px" }; - $: svgPath = computeSvgPath(direction, origin, majorMarkSpacing, minorDivisions, microDivisions, rulerLength); - $: svgTexts = computeSvgTexts(direction, origin, majorMarkSpacing, numberInterval, rulerLength); - - function computeSvgPath(direction: RulerDirection, origin: number, majorMarkSpacing: number, minorDivisions: number, microDivisions: number, rulerLength: number): string { - const isVertical = direction === "Vertical"; - const lineDirection = isVertical ? "H" : "V"; - - const offsetStart = mod(origin, majorMarkSpacing); - const shiftedOffsetStart = offsetStart - majorMarkSpacing; + type Axis = { + sign: number; + vec: [number, number]; + }; + + $: axes = computeAxes(tilt); + $: isHorizontal = direction === "Horizontal"; + $: trackedAxis = isHorizontal ? axes.horiz : axes.vert; + $: otherAxis = isHorizontal ? axes.vert : axes.horiz; + $: stretchFactor = 1 / (isHorizontal ? trackedAxis.vec[0] : trackedAxis.vec[1]); + $: stretchedSpacing = majorMarkSpacing * stretchFactor; + $: effectiveOrigin = computeEffectiveOrigin(direction, originX, originY, otherAxis); + $: svgPath = computeSvgPath(direction, effectiveOrigin, stretchedSpacing, minorDivisions, microDivisions, rulerLength, otherAxis); + $: svgTexts = computeSvgTexts(direction, effectiveOrigin, stretchedSpacing, numberInterval, rulerLength, trackedAxis); + + function computeAxes(tilt: number): { horiz: Axis; vert: Axis } { + const HALF_PI = Math.PI / 2; + const normTilt = ((tilt % (2 * Math.PI)) + 2 * Math.PI) % (2 * Math.PI); + const octant = Math.floor((normTilt + Math.PI / 4) / HALF_PI) % 4; + + const [c, s] = [Math.cos(tilt), Math.sin(tilt)]; + const posX: Axis = { sign: 1, vec: [c, s] }; + const posY: Axis = { sign: 1, vec: [-s, c] }; + const negX: Axis = { sign: -1, vec: [-c, -s] }; + const negY: Axis = { sign: -1, vec: [s, -c] }; + + if (octant === 0) return { horiz: posX, vert: posY }; + if (octant === 1) return { horiz: negY, vert: posX }; + if (octant === 2) return { horiz: negX, vert: negY }; + return { horiz: posY, vert: negX }; + } - const divisions = majorMarkSpacing / minorDivisions / microDivisions; - const majorMarksFrequency = minorDivisions * microDivisions; + function computeEffectiveOrigin(direction: RulerDirection, ox: number, oy: number, otherAxis: Axis): number { + const [vx, vy] = otherAxis.vec; + return direction === "Horizontal" ? ox - oy * (vx / vy) : oy - ox * (vy / vx); + } - let dPathAttribute = ""; + function computeSvgPath( + direction: RulerDirection, + effectiveOrigin: number, + stretchedSpacing: number, + minorDivisions: number, + microDivisions: number, + rulerLength: number, + otherAxis: Axis, + ): string { + const adaptive = stretchFactor > 1.3 ? { minor: minorDivisions, micro: 1 } : { minor: minorDivisions, micro: microDivisions }; + const divisions = stretchedSpacing / adaptive.minor / adaptive.micro; + const majorMarksFrequency = adaptive.minor * adaptive.micro; + const shiftedOffsetStart = mod(effectiveOrigin, stretchedSpacing) - stretchedSpacing; + + const [vx, vy] = otherAxis.vec; + // Tick direction: project outward from viewport edge into the ruler strip + const flip = direction === "Horizontal" ? (vy > 0 ? -1 : 1) : vx > 0 ? -1 : 1; + const [dx, dy] = [vx * flip, vy * flip]; + const [sxBase, syBase] = direction === "Horizontal" ? [0, RULER_THICKNESS] : [RULER_THICKNESS, 0]; + + let path = ""; let i = 0; - for (let location = shiftedOffsetStart; location < rulerLength; location += divisions) { - let length; - if (i % majorMarksFrequency === 0) length = MAJOR_MARK_THICKNESS; - else if (i % microDivisions === 0) length = MINOR_MARK_THICKNESS; - else length = MICRO_MARK_THICKNESS; + for (let loc = shiftedOffsetStart; loc < rulerLength + RULER_THICKNESS; loc += divisions) { + const length = i % majorMarksFrequency === 0 ? MAJOR_MARK_THICKNESS : i % adaptive.micro === 0 ? MINOR_MARK_THICKNESS : MICRO_MARK_THICKNESS; i += 1; - const destination = Math.round(location) + 0.5; - const startPoint = isVertical ? `${RULER_THICKNESS - length},${destination}` : `${destination},${RULER_THICKNESS - length}`; - dPathAttribute += `M${startPoint}${lineDirection}${RULER_THICKNESS} `; + const pos = Math.round(loc) + 0.5; + const [sx, sy] = direction === "Horizontal" ? [pos, syBase] : [sxBase, pos]; + path += `M${sx},${sy}l${dx * length},${dy * length} `; } - return dPathAttribute; + return path; } - function computeSvgTexts(direction: RulerDirection, origin: number, majorMarkSpacing: number, numberInterval: number, rulerLength: number): { transform: string; text: string }[] { + function computeSvgTexts( + direction: RulerDirection, + effectiveOrigin: number, + stretchedSpacing: number, + numberInterval: number, + rulerLength: number, + trackedAxis: Axis, + ): { transform: string; text: string }[] { const isVertical = direction === "Vertical"; - const offsetStart = mod(origin, majorMarkSpacing); - const shiftedOffsetStart = offsetStart - majorMarkSpacing; - - const svgTextCoordinates = []; + const shiftedOffsetStart = mod(effectiveOrigin, stretchedSpacing) - stretchedSpacing; + const increments = Math.round((shiftedOffsetStart - effectiveOrigin) / stretchedSpacing); + let labelNumber = increments * numberInterval * trackedAxis.sign; - let labelNumber = (Math.ceil(-origin / majorMarkSpacing) - 1) * numberInterval; + const results: { transform: string; text: string }[] = []; - for (let location = shiftedOffsetStart; location < rulerLength; location += majorMarkSpacing) { - const destination = Math.round(location); + for (let loc = shiftedOffsetStart; loc < rulerLength; loc += stretchedSpacing) { + const destination = Math.round(loc); const x = isVertical ? 9 : destination + 2; const y = isVertical ? destination + 1 : 9; let transform = `translate(${x} ${y})`; - if (isVertical) transform += " rotate(270)"; + if (isVertical) transform += " rotate(-90)"; - const text = numberInterval >= 1 ? `${labelNumber}` : labelNumber.toFixed(Math.abs(Math.log10(numberInterval))).replace(/\.0+$/, ""); + const num = Math.abs(labelNumber) < 1e-9 ? 0 : labelNumber; + const text = numberInterval >= 1 ? `${num}` : num.toFixed(Math.abs(Math.log10(numberInterval))).replace(/\.0+$/, ""); - svgTextCoordinates.push({ transform, text }); - - labelNumber += numberInterval; + results.push({ transform, text }); + labelNumber += numberInterval * trackedAxis.sign; } - return svgTextCoordinates; + return results; } export function resize() { if (!rulerInput) return; const isVertical = direction === "Vertical"; - const newLength = isVertical ? rulerInput.clientHeight : rulerInput.clientWidth; - const roundedUp = (Math.floor(newLength / majorMarkSpacing) + 1) * majorMarkSpacing; + const roundedUp = (Math.floor(newLength / stretchedSpacing) + 2) * stretchedSpacing; if (roundedUp !== rulerLength) { rulerLength = roundedUp; @@ -95,7 +143,6 @@ } } - // Modulo function that works for negative numbers, unlike the JS `%` operator function mod(n: number, m: number): number { const remainder = n % m; return Math.floor(remainder >= 0 ? remainder : remainder + m); diff --git a/frontend/src/messages.ts b/frontend/src/messages.ts index afa7903a54..012b4c8cd0 100644 --- a/frontend/src/messages.ts +++ b/frontend/src/messages.ts @@ -666,6 +666,8 @@ export class UpdateDocumentRulers extends JsMessage { readonly interval!: number; readonly visible!: boolean; + + readonly tilt!: number; } export class EyedropperPreviewImage { From 3659888d72be8e164865069de09d9957878b8584 Mon Sep 17 00:00:00 2001 From: Kulratan Thapar Date: Wed, 4 Mar 2026 11:21:51 +0000 Subject: [PATCH 2/5] Fix Ruler Text --- .../src/components/widgets/inputs/RulerInput.svelte | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/frontend/src/components/widgets/inputs/RulerInput.svelte b/frontend/src/components/widgets/inputs/RulerInput.svelte index 8a0a31c1b2..7a6488a926 100644 --- a/frontend/src/components/widgets/inputs/RulerInput.svelte +++ b/frontend/src/components/widgets/inputs/RulerInput.svelte @@ -36,7 +36,7 @@ $: stretchedSpacing = majorMarkSpacing * stretchFactor; $: effectiveOrigin = computeEffectiveOrigin(direction, originX, originY, otherAxis); $: svgPath = computeSvgPath(direction, effectiveOrigin, stretchedSpacing, minorDivisions, microDivisions, rulerLength, otherAxis); - $: svgTexts = computeSvgTexts(direction, effectiveOrigin, stretchedSpacing, numberInterval, rulerLength, trackedAxis); + $: svgTexts = computeSvgTexts(direction, effectiveOrigin, stretchedSpacing, numberInterval, rulerLength, trackedAxis, otherAxis); function computeAxes(tilt: number): { horiz: Axis; vert: Axis } { const HALF_PI = Math.PI / 2; @@ -101,9 +101,16 @@ numberInterval: number, rulerLength: number, trackedAxis: Axis, + otherAxis: Axis, ): { transform: string; text: string }[] { const isVertical = direction === "Vertical"; + // Compute the tick tip offset so labels align with the top of the slanted tick + const [vx, vy] = otherAxis.vec; + const flip = isVertical ? (vx > 0 ? -1 : 1) : vy > 0 ? -1 : 1; + const tipOffsetX = vx * flip * MAJOR_MARK_THICKNESS; + const tipOffsetY = vy * flip * MAJOR_MARK_THICKNESS; + const shiftedOffsetStart = mod(effectiveOrigin, stretchedSpacing) - stretchedSpacing; const increments = Math.round((shiftedOffsetStart - effectiveOrigin) / stretchedSpacing); let labelNumber = increments * numberInterval * trackedAxis.sign; @@ -112,8 +119,8 @@ for (let loc = shiftedOffsetStart; loc < rulerLength; loc += stretchedSpacing) { const destination = Math.round(loc); - const x = isVertical ? 9 : destination + 2; - const y = isVertical ? destination + 1 : 9; + const x = isVertical ? 9 : destination + 2 + tipOffsetX; + const y = isVertical ? destination + 1 + tipOffsetY : 9; let transform = `translate(${x} ${y})`; if (isVertical) transform += " rotate(-90)"; From aaa99244fcd0c2c6b0083efa7aaa0d9f700d0117 Mon Sep 17 00:00:00 2001 From: Kulratan Date: Wed, 11 Mar 2026 09:37:50 +0000 Subject: [PATCH 3/5] Address PR review --- .../widgets/inputs/RulerInput.svelte | 37 +++++++++---------- 1 file changed, 18 insertions(+), 19 deletions(-) diff --git a/frontend/src/components/widgets/inputs/RulerInput.svelte b/frontend/src/components/widgets/inputs/RulerInput.svelte index 79bb08961e..e0d152a7df 100644 --- a/frontend/src/components/widgets/inputs/RulerInput.svelte +++ b/frontend/src/components/widgets/inputs/RulerInput.svelte @@ -5,6 +5,7 @@ const MAJOR_MARK_THICKNESS = 16; const MINOR_MARK_THICKNESS = 6; const MICRO_MARK_THICKNESS = 3; + const TAU = 2 * Math.PI; type RulerDirection = "Horizontal" | "Vertical"; @@ -21,10 +22,7 @@ let rulerLength = 0; let svgBounds = { width: "0px", height: "0px" }; - type Axis = { - sign: number; - vec: [number, number]; - }; + type Axis = { sign: number; vec: [number, number] }; $: axes = computeAxes(tilt); $: isHorizontal = direction === "Horizontal"; @@ -33,13 +31,12 @@ $: stretchFactor = 1 / (isHorizontal ? trackedAxis.vec[0] : trackedAxis.vec[1]); $: stretchedSpacing = majorMarkSpacing * stretchFactor; $: effectiveOrigin = computeEffectiveOrigin(direction, originX, originY, otherAxis); - $: svgPath = computeSvgPath(direction, effectiveOrigin, stretchedSpacing, minorDivisions, microDivisions, rulerLength, otherAxis); + $: svgPath = computeSvgPath(direction, effectiveOrigin, stretchedSpacing, stretchFactor, minorDivisions, microDivisions, rulerLength, otherAxis); $: svgTexts = computeSvgTexts(direction, effectiveOrigin, stretchedSpacing, numberInterval, rulerLength, trackedAxis, otherAxis); function computeAxes(tilt: number): { horiz: Axis; vert: Axis } { - const HALF_PI = Math.PI / 2; - const normTilt = ((tilt % (2 * Math.PI)) + 2 * Math.PI) % (2 * Math.PI); - const octant = Math.floor((normTilt + Math.PI / 4) / HALF_PI) % 4; + const normTilt = ((tilt % TAU) + TAU) % TAU; + const octant = Math.floor((normTilt + Math.PI / 4) / (Math.PI / 2)) % 4; const [c, s] = [Math.cos(tilt), Math.sin(tilt)]; const posX: Axis = { sign: 1, vec: [c, s] }; @@ -62,6 +59,7 @@ direction: RulerDirection, effectiveOrigin: number, stretchedSpacing: number, + stretchFactor: number, minorDivisions: number, microDivisions: number, rulerLength: number, @@ -73,19 +71,21 @@ const shiftedOffsetStart = mod(effectiveOrigin, stretchedSpacing) - stretchedSpacing; const [vx, vy] = otherAxis.vec; - // Tick direction: project outward from viewport edge into the ruler strip const flip = direction === "Horizontal" ? (vy > 0 ? -1 : 1) : vx > 0 ? -1 : 1; const [dx, dy] = [vx * flip, vy * flip]; const [sxBase, syBase] = direction === "Horizontal" ? [0, RULER_THICKNESS] : [RULER_THICKNESS, 0]; let path = ""; let i = 0; - for (let loc = shiftedOffsetStart; loc < rulerLength + RULER_THICKNESS; loc += divisions) { - const length = i % majorMarksFrequency === 0 ? MAJOR_MARK_THICKNESS : i % adaptive.micro === 0 ? MINOR_MARK_THICKNESS : MICRO_MARK_THICKNESS; + for (let location = shiftedOffsetStart; location < rulerLength + RULER_THICKNESS; location += divisions) { + let length; + if (i % majorMarksFrequency === 0) length = MAJOR_MARK_THICKNESS; + else if (i % adaptive.micro === 0) length = MINOR_MARK_THICKNESS; + else length = MICRO_MARK_THICKNESS; i += 1; - const pos = Math.round(loc) + 0.5; - const [sx, sy] = direction === "Horizontal" ? [pos, syBase] : [sxBase, pos]; + const destination = Math.round(location) + 0.5; + const [sx, sy] = direction === "Horizontal" ? [destination, syBase] : [sxBase, destination]; path += `M${sx},${sy}l${dx * length},${dy * length} `; } @@ -103,7 +103,6 @@ ): { transform: string; text: string }[] { const isVertical = direction === "Vertical"; - // Compute the tick tip offset so labels align with the top of the slanted tick const [vx, vy] = otherAxis.vec; const flip = isVertical ? (vx > 0 ? -1 : 1) : vy > 0 ? -1 : 1; const tipOffsetX = vx * flip * MAJOR_MARK_THICKNESS; @@ -115,16 +114,15 @@ const results: { transform: string; text: string }[] = []; - for (let loc = shiftedOffsetStart; loc < rulerLength; loc += stretchedSpacing) { - const destination = Math.round(loc); + for (let location = shiftedOffsetStart; location < rulerLength; location += stretchedSpacing) { + const destination = Math.round(location); const x = isVertical ? 9 : destination + 2 + tipOffsetX; const y = isVertical ? destination + 1 + tipOffsetY : 9; let transform = `translate(${x} ${y})`; - if (isVertical) transform += " rotate(-90)"; + if (isVertical) transform += " rotate(270)"; - const num = Math.abs(labelNumber) < 1e-9 ? 0 : labelNumber; - const text = numberInterval >= 1 ? `${num}` : num.toFixed(Math.abs(Math.log10(numberInterval))).replace(/\.0+$/, ""); + const text = numberInterval >= 1 ? `${labelNumber}` : labelNumber.toFixed(Math.abs(Math.log10(numberInterval))).replace(/\.0+$/, ""); results.push({ transform, text }); labelNumber += numberInterval * trackedAxis.sign; @@ -148,6 +146,7 @@ } } + // Modulo function that works for negative numbers, unlike the JS `%` operator function mod(n: number, m: number): number { const remainder = n % m; return Math.floor(remainder >= 0 ? remainder : remainder + m); From 1d96c5c0556693aa01c1823a7b81116d6ceca9ba Mon Sep 17 00:00:00 2001 From: Kulcode <152772205+jsjgdh@users.noreply.github.com> Date: Wed, 11 Mar 2026 15:59:20 +0530 Subject: [PATCH 4/5] Fix per review Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com> --- frontend/src/components/widgets/inputs/RulerInput.svelte | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/frontend/src/components/widgets/inputs/RulerInput.svelte b/frontend/src/components/widgets/inputs/RulerInput.svelte index e0d152a7df..e3bd23d170 100644 --- a/frontend/src/components/widgets/inputs/RulerInput.svelte +++ b/frontend/src/components/widgets/inputs/RulerInput.svelte @@ -122,7 +122,8 @@ let transform = `translate(${x} ${y})`; if (isVertical) transform += " rotate(270)"; - const text = numberInterval >= 1 ? `${labelNumber}` : labelNumber.toFixed(Math.abs(Math.log10(numberInterval))).replace(/\.0+$/, ""); + const num = Math.abs(labelNumber) < 1e-9 ? 0 : labelNumber; + const text = numberInterval >= 1 ? `${num}` : num.toFixed(Math.abs(Math.log10(numberInterval))).replace(/\.0+$/, ""); results.push({ transform, text }); labelNumber += numberInterval * trackedAxis.sign; From 3697b7db7d73e7072550d0f55bcc8f43c0cd4d6a Mon Sep 17 00:00:00 2001 From: Keavon Chambers Date: Mon, 9 Mar 2026 16:28:26 -0700 Subject: [PATCH 5/5] Prep for the RC4 release of the desktop app --- editor/src/messages/portfolio/portfolio_message_handler.rs | 2 +- frontend/src/components/panels/Welcome.svelte | 2 +- frontend/src/components/window/MainWindow.svelte | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/editor/src/messages/portfolio/portfolio_message_handler.rs b/editor/src/messages/portfolio/portfolio_message_handler.rs index 5f00696c2b..dbc9c2d182 100644 --- a/editor/src/messages/portfolio/portfolio_message_handler.rs +++ b/editor/src/messages/portfolio/portfolio_message_handler.rs @@ -1057,7 +1057,7 @@ impl MessageHandler> for Portfolio } PortfolioMessage::RequestStatusBarInfoLayout => { #[cfg(not(target_family = "wasm"))] - let widgets = vec![TextLabel::new("Graphite 1.0.0-RC3").disabled(true).widget_instance()]; // TODO: After the RCs, call this "Graphite (beta) x.y.z" + let widgets = vec![TextLabel::new("Graphite 1.0.0-RC4").disabled(true).widget_instance()]; // TODO: After the RCs, call this "Graphite (beta) x.y.z" #[cfg(target_family = "wasm")] let widgets = vec![]; diff --git a/frontend/src/components/panels/Welcome.svelte b/frontend/src/components/panels/Welcome.svelte index 1c908286a3..49101abc37 100644 --- a/frontend/src/components/panels/Welcome.svelte +++ b/frontend/src/components/panels/Welcome.svelte @@ -52,7 +52,7 @@ {#if isPlatformNative()} - You are testing Release Candidate 3 of the 1.0 desktop release. Please regularly check Discord for the next testing build and report issues you encounter. + You are testing Release Candidate 4 of the 1.0 desktop release. Please regularly check Discord for the next testing build and report issues you encounter. {/if} diff --git a/frontend/src/components/window/MainWindow.svelte b/frontend/src/components/window/MainWindow.svelte index 252c417425..de83f3ab18 100644 --- a/frontend/src/components/window/MainWindow.svelte +++ b/frontend/src/components/window/MainWindow.svelte @@ -31,7 +31,7 @@ {#if $tooltip.visible} {/if} - {#if isPlatformNative() && new Date() > new Date("2026-03-15")} + {#if isPlatformNative() && new Date() > new Date("2026-04-30")}