Fix: background splitting bug
This commit is contained in:
35
assets/sprite-packs/demogorgon/manifest.json
Normal file
35
assets/sprite-packs/demogorgon/manifest.json
Normal file
@@ -0,0 +1,35 @@
|
||||
{
|
||||
"id": "demogorgon",
|
||||
"version": "1",
|
||||
"image": "sprite.png",
|
||||
"frame_width": 512,
|
||||
"frame_height": 512,
|
||||
"animations": [
|
||||
{
|
||||
"name": "idle",
|
||||
"fps": 6,
|
||||
"frames": [0, 1]
|
||||
},
|
||||
{
|
||||
"name": "active",
|
||||
"fps": 10,
|
||||
"frames": [1, 0]
|
||||
},
|
||||
{
|
||||
"name": "success",
|
||||
"fps": 10,
|
||||
"frames": [0, 1, 0],
|
||||
"one_shot": true
|
||||
},
|
||||
{
|
||||
"name": "error",
|
||||
"fps": 8,
|
||||
"frames": [1, 0, 1],
|
||||
"one_shot": true
|
||||
}
|
||||
],
|
||||
"anchor": {
|
||||
"x": 0.5,
|
||||
"y": 1.0
|
||||
}
|
||||
}
|
||||
BIN
assets/sprite-packs/demogorgon/sprite.png
Normal file
BIN
assets/sprite-packs/demogorgon/sprite.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 7.6 MiB |
@@ -10,6 +10,7 @@
|
||||
"core:window:allow-set-position",
|
||||
"core:window:allow-inner-size",
|
||||
"core:window:allow-outer-position",
|
||||
"core:window:allow-current-monitor",
|
||||
"core:event:allow-listen",
|
||||
"core:event:allow-unlisten"
|
||||
]
|
||||
|
||||
@@ -1 +1 @@
|
||||
{"default":{"identifier":"default","description":"Default capability for sprimo-tauri main window runtime APIs.","local":true,"windows":["*"],"permissions":["core:default","core:window:allow-start-dragging","core:window:allow-set-size","core:window:allow-set-position","core:window:allow-inner-size","core:window:allow-outer-position","core:event:allow-listen","core:event:allow-unlisten"]}}
|
||||
{"default":{"identifier":"default","description":"Default capability for sprimo-tauri main window runtime APIs.","local":true,"windows":["*"],"permissions":["core:default","core:window:allow-start-dragging","core:window:allow-set-size","core:window:allow-set-position","core:window:allow-inner-size","core:window:allow-outer-position","core:window:allow-current-monitor","core:event:allow-listen","core:event:allow-unlisten"]}}
|
||||
@@ -134,6 +134,14 @@ Bevy window-management verification workflow: `docs/BEVY_WINDOW_VERIFICATION.md`
|
||||
- run repeated character switch cycles (`default` <-> `ferris`) and move scale slider each cycle
|
||||
- ensure no runtime frontend exception is shown (debug overlay/console)
|
||||
- ensure no visible magenta fringe remains around sprite edges after chroma-key conversion
|
||||
12. Verify packaged tauri scale anchoring and bounds:
|
||||
- repeated scale changes resize around window center (no consistent bottom-right drift)
|
||||
- window remains visible on the current monitor (no off-screen drift)
|
||||
- no runtime scale-path exception appears (for example monitor lookup API errors)
|
||||
- no runtime position-arg exceptions appear during scale (e.g. float passed to integer position API)
|
||||
- at large scale values (>= 1.8), full sprite remains visible without clipping
|
||||
13. Verify packaged tauri frontend freshness:
|
||||
- confirm package run reflects latest `frontend/tauri-ui` changes (no stale embedded UI bundle)
|
||||
|
||||
### Packaged Mode (Required Once Tauri Packaging Exists)
|
||||
|
||||
|
||||
@@ -114,6 +114,13 @@ An issue touching Tauri runtime behaviors must satisfy all requirements before `
|
||||
- scaling behavior remains responsive after each pack switch
|
||||
8. Chroma-key quality check:
|
||||
- verify no visible magenta (`#FF00FF`) fringe remains around sprite edges in normal runtime view
|
||||
9. Scale anchor and bounds check:
|
||||
- repeated scale changes should keep window centered without directional drift
|
||||
- window must remain within current monitor bounds during scale adjustments
|
||||
- no runtime error is allowed from monitor lookup during scale operations (e.g. `currentMonitor`
|
||||
API mismatch)
|
||||
- verify window resize uses consistent coordinate units (no accumulated drift over 20 scale changes)
|
||||
- no runtime command/type error from position updates (e.g. `set_position` expects integer coords)
|
||||
|
||||
## Settings Window Checklist
|
||||
|
||||
|
||||
@@ -2,7 +2,13 @@ import React from "react";
|
||||
import ReactDOM from "react-dom/client";
|
||||
import { invoke } from "@tauri-apps/api/core";
|
||||
import { listen } from "@tauri-apps/api/event";
|
||||
import { LogicalPosition, LogicalSize, getCurrentWindow } from "@tauri-apps/api/window";
|
||||
import {
|
||||
PhysicalPosition,
|
||||
PhysicalSize,
|
||||
currentMonitor,
|
||||
getCurrentWindow,
|
||||
monitorFromPoint
|
||||
} from "@tauri-apps/api/window";
|
||||
import { PixiPetRenderer, type UiSpritePack, type UiSnapshot } from "./renderer/pixi_pet";
|
||||
import "./styles.css";
|
||||
|
||||
@@ -45,29 +51,58 @@ function fittedWindowSize(
|
||||
scale: number
|
||||
): { width: number; height: number } {
|
||||
const safeScale = Number.isFinite(scale) && scale > 0 ? scale : 1;
|
||||
const width = Math.max(frameWidth * safeScale + WINDOW_PADDING, MIN_WINDOW_SIZE);
|
||||
const height = Math.max(frameHeight * safeScale + WINDOW_PADDING, MIN_WINDOW_SIZE);
|
||||
const width = Math.round(Math.max(frameWidth * safeScale + WINDOW_PADDING, MIN_WINDOW_SIZE));
|
||||
const height = Math.round(Math.max(frameHeight * safeScale + WINDOW_PADDING, MIN_WINDOW_SIZE));
|
||||
return { width, height };
|
||||
}
|
||||
|
||||
async function fitWindowForScale(pack: UiSpritePack, scale: number): Promise<void> {
|
||||
const window = getCurrentWindow();
|
||||
const [outerPosition, innerSize] = await Promise.all([window.outerPosition(), window.innerSize()]);
|
||||
|
||||
const target = fittedWindowSize(pack.frame_width, pack.frame_height, scale);
|
||||
const widthChanged = Math.abs(target.width - innerSize.width) > SIZE_EPSILON;
|
||||
const heightChanged = Math.abs(target.height - innerSize.height) > SIZE_EPSILON;
|
||||
const centerX = outerPosition.x + innerSize.width / 2;
|
||||
const centerY = outerPosition.y + innerSize.height / 2;
|
||||
let targetWidth = target.width;
|
||||
let targetHeight = target.height;
|
||||
let targetX = centerX - targetWidth / 2;
|
||||
let targetY = centerY - targetHeight / 2;
|
||||
let monitor:
|
||||
| {
|
||||
position: { x: number; y: number };
|
||||
size: { width: number; height: number };
|
||||
workArea: { position: { x: number; y: number }; size: { width: number; height: number } };
|
||||
}
|
||||
| null = null;
|
||||
|
||||
try {
|
||||
monitor = (await monitorFromPoint(centerX, centerY)) ?? (await currentMonitor());
|
||||
} catch {
|
||||
monitor = null;
|
||||
}
|
||||
|
||||
if (monitor !== null) {
|
||||
targetWidth = Math.min(targetWidth, monitor.workArea.size.width);
|
||||
targetHeight = Math.min(targetHeight, monitor.workArea.size.height);
|
||||
targetX = centerX - targetWidth / 2;
|
||||
targetY = centerY - targetHeight / 2;
|
||||
}
|
||||
|
||||
const widthChanged = Math.abs(targetWidth - innerSize.width) > SIZE_EPSILON;
|
||||
const heightChanged = Math.abs(targetHeight - innerSize.height) > SIZE_EPSILON;
|
||||
if (!widthChanged && !heightChanged) {
|
||||
return;
|
||||
}
|
||||
|
||||
const deltaWidth = target.width - innerSize.width;
|
||||
const deltaHeight = target.height - innerSize.height;
|
||||
const targetX = outerPosition.x - deltaWidth / 2;
|
||||
const targetY = outerPosition.y - deltaHeight;
|
||||
|
||||
await window.setSize(new LogicalSize(target.width, target.height));
|
||||
await window.setPosition(new LogicalPosition(targetX, targetY));
|
||||
await window.setSize(new PhysicalSize(targetWidth, targetHeight));
|
||||
if (monitor !== null) {
|
||||
const minX = Math.round(monitor.workArea.position.x);
|
||||
const minY = Math.round(monitor.workArea.position.y);
|
||||
const maxX = Math.round(monitor.workArea.position.x + monitor.workArea.size.width - targetWidth);
|
||||
const maxY = Math.round(monitor.workArea.position.y + monitor.workArea.size.height - targetHeight);
|
||||
targetX = maxX < minX ? minX : Math.min(Math.max(targetX, minX), maxX);
|
||||
targetY = maxY < minY ? minY : Math.min(Math.max(targetY, minY), maxY);
|
||||
}
|
||||
await window.setPosition(new PhysicalPosition(Math.round(targetX), Math.round(targetY)));
|
||||
}
|
||||
|
||||
function MainOverlayWindow(): JSX.Element {
|
||||
|
||||
@@ -33,6 +33,22 @@ export type UiSnapshot = {
|
||||
};
|
||||
|
||||
type AnimationMap = Map<string, UiAnimationClip>;
|
||||
const KEY_R = 0xff;
|
||||
const KEY_G = 0x00;
|
||||
const KEY_B = 0xff;
|
||||
const FALLBACK_MIN_CONNECTED_RATIO = 0.005;
|
||||
const CONNECTED_HUE_MIN = 270;
|
||||
const CONNECTED_HUE_MAX = 350;
|
||||
const CONNECTED_SAT_MIN = 0.25;
|
||||
const CONNECTED_VAL_MIN = 0.08;
|
||||
const FALLBACK_HUE_MIN = 255;
|
||||
const FALLBACK_HUE_MAX = 355;
|
||||
const FALLBACK_SAT_MIN = 0.15;
|
||||
const FALLBACK_VAL_MIN = 0.04;
|
||||
const HALO_HUE_MIN = 245;
|
||||
const HALO_HUE_MAX = 355;
|
||||
const HALO_SAT_MIN = 0.15;
|
||||
const HALO_VAL_MIN = 0.04;
|
||||
|
||||
export class PixiPetRenderer {
|
||||
private app: Application;
|
||||
@@ -101,28 +117,144 @@ export class PixiPetRenderer {
|
||||
ctx.drawImage(image, 0, 0);
|
||||
const frame = ctx.getImageData(0, 0, canvas.width, canvas.height);
|
||||
const data = frame.data;
|
||||
const keyR = 0xff;
|
||||
const keyG = 0x00;
|
||||
const keyB = 0xff;
|
||||
const hardTolerance = 22;
|
||||
const softTolerance = 46;
|
||||
for (let i = 0; i < data.length; i += 4) {
|
||||
const dr = Math.abs(data[i] - keyR);
|
||||
const dg = Math.abs(data[i + 1] - keyG);
|
||||
const db = Math.abs(data[i + 2] - keyB);
|
||||
const maxDistance = Math.max(dr, dg, db);
|
||||
if (maxDistance <= hardTolerance) {
|
||||
data[i + 3] = 0;
|
||||
const width = canvas.width;
|
||||
const height = canvas.height;
|
||||
const pixelCount = width * height;
|
||||
const isKeyLike = new Uint8Array(pixelCount);
|
||||
const removedBg = new Uint8Array(pixelCount);
|
||||
const queue = new Int32Array(pixelCount);
|
||||
let head = 0;
|
||||
let tail = 0;
|
||||
|
||||
const indexFor = (x: number, y: number): number => y * width + x;
|
||||
const channelOffset = (index: number): number => index * 4;
|
||||
const enqueueIfKeyLike = (x: number, y: number): void => {
|
||||
const idx = indexFor(x, y);
|
||||
if (isKeyLike[idx] === 1 && removedBg[idx] === 0) {
|
||||
removedBg[idx] = 1;
|
||||
queue[tail] = idx;
|
||||
tail += 1;
|
||||
}
|
||||
};
|
||||
|
||||
for (let idx = 0; idx < pixelCount; idx += 1) {
|
||||
const offset = channelOffset(idx);
|
||||
const [h, s, v] = PixiPetRenderer.rgbToHsv(
|
||||
data[offset],
|
||||
data[offset + 1],
|
||||
data[offset + 2]
|
||||
);
|
||||
if (
|
||||
PixiPetRenderer.isHueInRange(h, CONNECTED_HUE_MIN, CONNECTED_HUE_MAX) &&
|
||||
s >= CONNECTED_SAT_MIN &&
|
||||
v >= CONNECTED_VAL_MIN
|
||||
) {
|
||||
isKeyLike[idx] = 1;
|
||||
}
|
||||
}
|
||||
|
||||
for (let x = 0; x < width; x += 1) {
|
||||
enqueueIfKeyLike(x, 0);
|
||||
enqueueIfKeyLike(x, height - 1);
|
||||
}
|
||||
for (let y = 1; y < height - 1; y += 1) {
|
||||
enqueueIfKeyLike(0, y);
|
||||
enqueueIfKeyLike(width - 1, y);
|
||||
}
|
||||
|
||||
while (head < tail) {
|
||||
const idx = queue[head];
|
||||
head += 1;
|
||||
const x = idx % width;
|
||||
const y = Math.floor(idx / width);
|
||||
if (x > 0) {
|
||||
enqueueIfKeyLike(x - 1, y);
|
||||
}
|
||||
if (x + 1 < width) {
|
||||
enqueueIfKeyLike(x + 1, y);
|
||||
}
|
||||
if (y > 0) {
|
||||
enqueueIfKeyLike(x, y - 1);
|
||||
}
|
||||
if (y + 1 < height) {
|
||||
enqueueIfKeyLike(x, y + 1);
|
||||
}
|
||||
}
|
||||
|
||||
const connectedRemovedCount = tail;
|
||||
for (let idx = 0; idx < pixelCount; idx += 1) {
|
||||
if (removedBg[idx] !== 1) {
|
||||
continue;
|
||||
}
|
||||
if (maxDistance <= softTolerance) {
|
||||
const alphaScale =
|
||||
(maxDistance - hardTolerance) / (softTolerance - hardTolerance);
|
||||
const suppress = 1 - alphaScale;
|
||||
data[i + 3] = Math.round(data[i + 3] * alphaScale);
|
||||
// Remove magenta spill from antialiased edges after alpha reduction.
|
||||
data[i] = Math.round(data[i] * (1 - 0.4 * suppress));
|
||||
data[i + 2] = Math.round(data[i + 2] * (1 - 0.4 * suppress));
|
||||
const offset = channelOffset(idx);
|
||||
data[offset + 3] = 0;
|
||||
}
|
||||
|
||||
const needsFallback =
|
||||
connectedRemovedCount / Math.max(pixelCount, 1) < FALLBACK_MIN_CONNECTED_RATIO;
|
||||
if (needsFallback) {
|
||||
for (let idx = 0; idx < pixelCount; idx += 1) {
|
||||
const offset = channelOffset(idx);
|
||||
const [h, s, v] = PixiPetRenderer.rgbToHsv(
|
||||
data[offset],
|
||||
data[offset + 1],
|
||||
data[offset + 2]
|
||||
);
|
||||
const maxDistanceFromHardKey = PixiPetRenderer.maxColorDistance(
|
||||
data[offset],
|
||||
data[offset + 1],
|
||||
data[offset + 2],
|
||||
KEY_R,
|
||||
KEY_G,
|
||||
KEY_B
|
||||
);
|
||||
if (
|
||||
(PixiPetRenderer.isHueInRange(h, FALLBACK_HUE_MIN, FALLBACK_HUE_MAX) &&
|
||||
s >= FALLBACK_SAT_MIN &&
|
||||
v >= FALLBACK_VAL_MIN) ||
|
||||
maxDistanceFromHardKey <= 96
|
||||
) {
|
||||
data[offset + 3] = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (let y = 0; y < height; y += 1) {
|
||||
for (let x = 0; x < width; x += 1) {
|
||||
const idx = indexFor(x, y);
|
||||
if (data[channelOffset(idx) + 3] === 0) {
|
||||
continue;
|
||||
}
|
||||
let touchesBackground = false;
|
||||
if (x > 0 && data[channelOffset(indexFor(x - 1, y)) + 3] === 0) {
|
||||
touchesBackground = true;
|
||||
} else if (x + 1 < width && data[channelOffset(indexFor(x + 1, y)) + 3] === 0) {
|
||||
touchesBackground = true;
|
||||
} else if (y > 0 && data[channelOffset(indexFor(x, y - 1)) + 3] === 0) {
|
||||
touchesBackground = true;
|
||||
} else if (y + 1 < height && data[channelOffset(indexFor(x, y + 1)) + 3] === 0) {
|
||||
touchesBackground = true;
|
||||
}
|
||||
if (!touchesBackground) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const offset = channelOffset(idx);
|
||||
const [h, s, v] = PixiPetRenderer.rgbToHsv(
|
||||
data[offset],
|
||||
data[offset + 1],
|
||||
data[offset + 2]
|
||||
);
|
||||
if (
|
||||
!PixiPetRenderer.isHueInRange(h, HALO_HUE_MIN, HALO_HUE_MAX) ||
|
||||
s < HALO_SAT_MIN ||
|
||||
v < HALO_VAL_MIN
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
data[offset] = Math.round(data[offset] * 0.72);
|
||||
data[offset + 2] = Math.round(data[offset + 2] * 0.72);
|
||||
data[offset + 3] = Math.round(data[offset + 3] * 0.86);
|
||||
}
|
||||
}
|
||||
ctx.putImageData(frame, 0, 0);
|
||||
@@ -135,6 +267,53 @@ export class PixiPetRenderer {
|
||||
});
|
||||
}
|
||||
|
||||
private static maxColorDistance(
|
||||
r: number,
|
||||
g: number,
|
||||
b: number,
|
||||
keyR: number,
|
||||
keyG: number,
|
||||
keyB: number
|
||||
): number {
|
||||
const dr = Math.abs(r - keyR);
|
||||
const dg = Math.abs(g - keyG);
|
||||
const db = Math.abs(b - keyB);
|
||||
return Math.max(dr, dg, db);
|
||||
}
|
||||
|
||||
private static rgbToHsv(r: number, g: number, b: number): [number, number, number] {
|
||||
const rf = r / 255;
|
||||
const gf = g / 255;
|
||||
const bf = b / 255;
|
||||
const max = Math.max(rf, gf, bf);
|
||||
const min = Math.min(rf, gf, bf);
|
||||
const delta = max - min;
|
||||
|
||||
let hue = 0;
|
||||
if (delta > 0) {
|
||||
if (max === rf) {
|
||||
hue = 60 * (((gf - bf) / delta) % 6);
|
||||
} else if (max === gf) {
|
||||
hue = 60 * ((bf - rf) / delta + 2);
|
||||
} else {
|
||||
hue = 60 * ((rf - gf) / delta + 4);
|
||||
}
|
||||
}
|
||||
if (hue < 0) {
|
||||
hue += 360;
|
||||
}
|
||||
const saturation = max === 0 ? 0 : delta / max;
|
||||
const value = max;
|
||||
return [hue, saturation, value];
|
||||
}
|
||||
|
||||
private static isHueInRange(hue: number, min: number, max: number): boolean {
|
||||
if (min <= max) {
|
||||
return hue >= min && hue <= max;
|
||||
}
|
||||
return hue >= min || hue <= max;
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
if (this.disposed) {
|
||||
return;
|
||||
|
||||
@@ -16,6 +16,8 @@ P1
|
||||
- `issues/screenshots/issue4.png`
|
||||
- `issues/screenshots/issue4-b.png`
|
||||
- `issues/screenshots/issue4-c.png`
|
||||
- `issues/screenshots/issue4-after-fix2-2026-02-14-145819.png`
|
||||
- `issues/screenshots/issue4-after-fix4-2026-02-14-153233.png`
|
||||
|
||||
## Summary
|
||||
|
||||
@@ -57,6 +59,17 @@ and scaling becomes ineffective after the error.
|
||||
the view in a broken/cropped state on creation failures.
|
||||
5. Chroma-key conversion tolerance removed most `#FF00FF` background but still left magenta fringe
|
||||
on anti-aliased edges.
|
||||
6. Scale fit used repeated position deltas and caused directional drift during repeated resizing.
|
||||
7. API mismatch in tauri window module:
|
||||
- runtime used `getCurrentWindow().currentMonitor()` but this API version exposes monitor lookup as
|
||||
module function (`currentMonitor`), causing `TypeError` and skipping window fit.
|
||||
8. Scale position math mixed physical window metrics (`outerPosition`/`innerSize`) with logical
|
||||
set operations (`LogicalSize`/`LogicalPosition`), reintroducing cumulative drift in some DPI
|
||||
contexts.
|
||||
9. Ferris background keying needed adaptive key detection; fixed `#FF00FF` assumptions were still
|
||||
too brittle for packaged atlas variants.
|
||||
10. Scale-path physical positioning used non-integer coordinates in `setPosition`, triggering
|
||||
runtime arg errors (`expected i32`) and bypassing window fit updates.
|
||||
|
||||
## Fix Plan
|
||||
|
||||
@@ -93,6 +106,32 @@ Implemented:
|
||||
- Made pack reload transactional (keep old renderer until new renderer creation succeeds).
|
||||
- Improved fit-window flow so scale apply continues after reload retries.
|
||||
- Added targeted diagnostics for reload failures.
|
||||
6. `frontend/tauri-ui/src/main.tsx`
|
||||
- Changed scaling anchor to window center and clamped resized window position within current
|
||||
monitor bounds.
|
||||
7. `frontend/tauri-ui/src/renderer/pixi_pet.ts`
|
||||
- Replaced tolerance-only chroma key with border-connected `#FF00FF` background flood-fill removal
|
||||
and localized edge halo suppression.
|
||||
8. `crates/sprimo-tauri/capabilities/default.json`
|
||||
- Added `core:window:allow-current-monitor` permission for monitor bounds clamping.
|
||||
9. `frontend/tauri-ui/src/main.tsx`
|
||||
- switched monitor lookup to module-level `currentMonitor()` with safe fallback so window scaling
|
||||
still applies even if monitor introspection is unavailable.
|
||||
10. `frontend/tauri-ui/src/renderer/pixi_pet.ts`
|
||||
- added fallback global key cleanup when border-connected background detection is too sparse.
|
||||
11. `frontend/tauri-ui/src/main.tsx`
|
||||
- moved scale resizing and positioning to physical units (`PhysicalSize`/`PhysicalPosition`) and
|
||||
monitor selection at window-center point (`monitorFromPoint`).
|
||||
12. `frontend/tauri-ui/src/renderer/pixi_pet.ts`
|
||||
- added adaptive border-derived key color selection with fallback key cleanup pass.
|
||||
13. `scripts/package_windows.py`
|
||||
- tauri packaging now explicitly rebuilds UI bundle to avoid stale embedded `dist` output.
|
||||
14. `frontend/tauri-ui/src/main.tsx`
|
||||
- enforced integer physical positioning and monitor work-area size clamping to prevent set-position
|
||||
arg failures and large-scale clipping.
|
||||
15. `frontend/tauri-ui/src/renderer/pixi_pet.ts`
|
||||
- switched ferris cleanup to hue/saturation/value magenta-band masking with connected background
|
||||
removal and stronger fallback cleanup.
|
||||
|
||||
## Verification
|
||||
|
||||
@@ -122,6 +161,14 @@ Implemented:
|
||||
- `2026-02-14 00:00` - codex - `Fix Implemented` - applied 8x7 generic splitter policy and pack-ID correction.
|
||||
- `2026-02-14 00:00` - reporter - `In Progress` - reported `issue4-after-fix1` still failing in packaged runtime.
|
||||
- `2026-02-14 00:00` - codex - `Fix Implemented` - hardened renderer reload/dispose and chroma-key edge cleanup.
|
||||
- `2026-02-14 00:00` - reporter - `In Progress` - remaining magenta ferris edge + scale drift reported.
|
||||
- `2026-02-14 00:00` - codex - `Fix Implemented` - switched to border-connected chroma-key removal and center-anchored, monitor-clamped scale fit.
|
||||
- `2026-02-14 00:00` - reporter - `In Progress` - reported `currentMonitor` TypeError and ferris magenta background still visible.
|
||||
- `2026-02-14 00:00` - codex - `Fix Implemented` - corrected monitor API call and added fallback chroma cleanup pass.
|
||||
- `2026-02-14 00:00` - reporter - `In Progress` - reported ferris magenta background still visible and scale drift recurrence.
|
||||
- `2026-02-14 00:00` - codex - `Fix Implemented` - switched to physical-unit resize math, adaptive key detection, and packaging UI refresh enforcement.
|
||||
- `2026-02-14 00:00` - reporter - `In Progress` - reported default clipping, ferris background still present, and set_position float arg error.
|
||||
- `2026-02-14 00:00` - codex - `Fix Implemented` - added integer-safe physical setPosition and HSV magenta cleanup strategy.
|
||||
|
||||
## Closure
|
||||
|
||||
|
||||
@@ -153,6 +153,8 @@ def sha256_file(path: Path) -> str:
|
||||
def package(frontend: FrontendLayout) -> PackageLayout:
|
||||
version = read_version()
|
||||
ensure_assets()
|
||||
if frontend.id == "tauri":
|
||||
run(["npm", "--prefix", "frontend/tauri-ui", "run", "build"])
|
||||
binary = ensure_release_binary(frontend)
|
||||
runtime_files = ensure_runtime_files(frontend, binary.parent)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user