Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
12 changes: 9 additions & 3 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,14 @@ Format basiert auf [Keep a Changelog](https://keepachangelog.com/de/1.1.0/).

## [Unreleased]

## [1.0.7] - 2026-09-26

### Behoben / Fixed (Store-Icon weißer Rand — T-20260820-729932431)
- **Store-Kachel-Icons mit sichtbarem weißen Rand (`store_assets/Square44x44Logo.png`, `Square150x150Logo.png`, `Square310x310Logo.png`, `StoreLogo.png`, `Wide310x150Logo.png`)**: Diese Dateien hatten trotz RGBA-Modus einen opaken (nicht transparenten) Canvas-Hintergrund gebacken (Alpha 255 statt 0 rund um das Motiv, bei `Wide310x150Logo.png` im eingebetteten 150px-Mittelfeld) — sichtbar als weißer Kasten/Halo um das Icon-Motiv in Kachel, Startmenü, Taskleiste und Store-Listing.
- **Nachreview (merge-reviewer, claude-opus, Commit `aee6401`) fand einen Mangel** in einem ersten, per Direkt-Push auf `master` eingespielten Versuch (in 1.0.6 nicht mehr zurückgenommen, siehe unten): Die dort verwendeten Ersatzdateien (`icon_44x44.png` u. a.) zeigen eine ANDERE Gestaltung (gerahmte Variante mit cyanfarbenem Rahmen und weißer Sichel am Rand, kleineres Motiv) statt des ursprünglichen Vollflächen-Motivs — und `Wide310x150Logo.png` blieb dabei unentdeckt defekt.
- **Erster Korrekturversuch (PR #3, `edcb8fa`) noch nicht sauber:** Ursprüngliches Vollflächen-Motiv per Flood-Fill-Matting freigestellt, aber mit zu kleinem Farbdistanz-Schwellwert (60) — die eigentliche Antialiasing-Übergangszone am Rand der abgerundeten Ecke war nur 1 Pixel breit, hatte aber eine Farbdistanz von ~97 zum Hintergrund und blieb dadurch unverarbeitet: ein 1px heller Saum auf dunklem Grund (gemessen: bis zu 204 Randpixel mit Alpha ≥ 192 und Luminanz auf Schwarz bis 216, je nach Kachelgröße).
- **Korrigierter Fix (dieser Release):** Farbdistanz-Schwellwert auf 150 angehoben (deutlich über der gemessenen Übergangsdistanz von ~97, deutlich unter der Distanz zur soliden Motivfarbe von >300) — damit erfasst das Un-Premultiply die komplette Antialiasing-Übergangszone, kein Fransensaum mehr. Motiv unverändert (keine Neugestaltung). `icon_310x150.png` ist ein separates Marketing-Banner (nicht vom AppxManifest referenziert) und bleibt unverändert. Regressionstest `test_store_tiles_have_transparent_corners` um eine Luminanz-Prüfung auf Schwarz-Komposit erweitert, damit dieser Fehler künftig auffällt. Verifiziert per Kontaktbogen (5 Größen × Weiß/Schwarz/Grau/Akzentgrün) und Vergrößerung der abgerundeten Ecke: kein Halo, kein Fransensaum, keine hellen Randflächen. Per Pull Request gegen `master` (kein Direkt-Push).

## [1.0.6] - 2026-09-26

### Behoben / Fixed (Kontextmenü, Editor-Dirty-Flag, Qt-Übersetzung — T-20260926-912169808)
Expand All @@ -14,9 +22,7 @@ Format basiert auf [Keep a Changelog](https://keepachangelog.com/de/1.1.0/).
- **Speichern/Verwerfen-Dialog auf Englisch in der deutschen Version (`src/main.py`)**: Kein Qt-eigener Übersetzer installiert. `QTranslator` mit `qtbase_<lang>.qm` ergänzt (unabhängig von der App-eigenen `TranslationSystem`, kein Overwrite).
- **Mehrfach-Umbenennen/Diff/Einstellungen stürzen im Quellstart ab (`src/main.py`, `ExplorerPro.spec`)**: `translator.py` (Projektwurzel) fehlte auf `sys.path`; Spec um `pathex`/`datas` für Projektwurzel und `locales/` ergänzt.
- **9 neue automatisierte Regressionstests** (`tests/test_context_menu_actions.py`), Vollsuite 359 passed / 2 skipped. Review/Merge als zweites Modell: Repo `file-bricks/ExplorerPro#1`, Squash `264aa6eae60055f1ffb4ff041025920738c6ead2`.

### Behoben / Fixed (Store-Icon weißer Rand — T-20260820-729932431)
- **Store-Kachel-Icons mit sichtbarem weißen Rand (`store_assets/Square44x44Logo.png`, `Square150x150Logo.png`, `Square310x310Logo.png`, `StoreLogo.png`)**: Diese vier Dateien hatten trotz RGBA-Modus einen opaken (nicht transparenten) Canvas-Hintergrund gebacken (Alpha 255 an den Ecken statt 0) — sichtbar als weißer Kasten/Halo um das Icon-Motiv in Kachel, Startmenü und Store-Listing. Die im selben Ordner bereits korrekt transparenten Gegenstücke gleicher Größe und gleichen Motivs (`icon_44x44.png`, `icon_150x150.png`, `icon_310x310.png`, `icon_50x50.png`) haben die vier defekten Dateien ersetzt — Motiv unverändert (kein Redesign), nur echte Transparenz statt gebackenem Hintergrund. `Wide310x150Logo.png` war bereits korrekt; `icon_310x150.png` ist ein separates Marketing-Banner (nicht vom AppxManifest referenziert) und unverändert. Verifiziert per Kontaktbogen auf Weiß/Schwarz/Grau/Akzentgrün (kein Halo, keine hellen Ränder auf allen vier Hintergründen).
- **Bekannter Mangel dieses Releases:** Der ebenfalls in 1.0.6 enthaltene Icon-Fix-Versuch (direkt gepusht, `aee6401`) hatte das falsche Motiv und einen unentdeckt gebliebenen Defekt in `Wide310x150Logo.png` — die Store-Submission dieser Version läuft dennoch durch, weil die Kontextmenü-Fixes ankommen sollen; der korrigierte Icon-Fix folgt in 1.0.7.

### Behoben / Fixed (Bugsearch 2026-09-26)
- **Batch-Renamer, Kaskadierende Kollisionserkennung, Windows-Gerätenamens-Schutz und Nummerierungs-Affix-Integrität (`src/core/batch_rename_service.py`)**:
Expand Down
2 changes: 1 addition & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
name: ExplorerPro
type: project-docs
profile: STANDARD
version: 1.0.6
version: 1.0.7
created: 2026-07-22
updated: 2026-09-26
reason_last_change: "T-20260926-912169808: Kontextmenü-Tags/Metadaten, Editor-Dirty-Flag (modificationChanged), qtbase-Übersetzung, 359 Tests"
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
[![Umbrella: open--bricks](https://img.shields.io/badge/umbrella-open--bricks-blue.svg)](https://github.com/open-bricks)
[![Microsoft Store](https://img.shields.io/badge/Microsoft%20Store-Live-0078D7.svg?logo=windows)](https://apps.microsoft.com/detail/9P0X52WSHZ3Q)
[![LLM-Ready: llms.txt](https://img.shields.io/badge/LLM--Ready-llms.txt-success.svg)](llms.txt)
[![Version: 1.0.6](https://img.shields.io/badge/version-1.0.6-orange.svg)](CHANGELOG.md)
[![Version: 1.0.7](https://img.shields.io/badge/version-1.0.7-orange.svg)](CHANGELOG.md)
[![Last Checked](https://img.shields.io/badge/Last--Checked-2026--09--26-blue?style=flat-square)](CHANGELOG.md)

> [!NOTE]
Expand Down
2 changes: 1 addition & 1 deletion README_de.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
[![Dachverband: open--bricks](https://img.shields.io/badge/Dachverband-open--bricks-blue.svg)](https://github.com/open-bricks)
[![Microsoft Store](https://img.shields.io/badge/Microsoft%20Store-Live-0078D7.svg?logo=windows)](https://apps.microsoft.com/detail/9P0X52WSHZ3Q)
[![LLM-Ready: llms.txt](https://img.shields.io/badge/LLM--Ready-llms.txt-success.svg)](llms.txt)
[![Version: 1.0.6](https://img.shields.io/badge/Version-1.0.6-orange.svg)](CHANGELOG.md)
[![Version: 1.0.7](https://img.shields.io/badge/Version-1.0.7-orange.svg)](CHANGELOG.md)
[![Stand](https://img.shields.io/badge/Stand-2026--09--26-blue?style=flat-square)](CHANGELOG.md)

> [!NOTE]
Expand Down
2 changes: 1 addition & 1 deletion llms.txt
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ License: AGPL-3.0
Microsoft Store Identity: Geiger.ExplorerPro (Store-ID: 9P0X52WSHZ3Q)

Last-checked: 2026-09-26
Version: 1.0.6
Version: 1.0.7

## Search Phrases

Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = "explorerpro"
version = "1.0.6"
version = "1.0.7"
description = "Datei-Explorer-Suite mit Editor, PDF-Viewer und Datenanalyse"
authors = [{ name = "Lukas Geiger", email = "support@lukasgeiger.com" }]
requires-python = ">=3.10"
Expand Down
108 changes: 108 additions & 0 deletions scripts/defringe_store_tile.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
"""Freistellen: entfernt einen naeher-am-weissen Canvas-Hintergrund liegenden,
mit dem Bild verbundenen Bereich per Flood-Fill und entfernt dabei per
Un-Premultiply auch den weissen Fransen-/Halo-Saum an den Kanten.
Motiv bleibt unveraendert; nur Hintergrund/Rand wird transparent.

bg_thresh muss die GESAMTE Antialiasing-Uebergangszone zwischen Hintergrund
und Motiv abdecken, nicht nur "nah am Hintergrund". Bei T-20260820-729932431
war der Uebergang am Rand der abgerundeten Ecke nur 1 Pixel breit, aber mit
einer Farbdistanz von ~97 zum Hintergrund -- ein zu kleiner bg_thresh (60)
liess genau dieses Pixel unverarbeitet (voll opak, urspruengliche helle
Mischfarbe) zurueck: ein 1px heller Saum auf dunklem Grund. 150 hat sich
als sicherer Wert erwiesen (deutlich unter der Distanz zur soliden
Motivfarbe von >300, deutlich ueber der gemessenen Uebergangsdistanz)."""
import sys
import math
from collections import deque
from PIL import Image


def defringe(path, out_path, bg=None, bg_thresh=150):
im = Image.open(path).convert("RGBA")
w, h = im.size
px = im.load()

if bg is None:
# Referenz-Hintergrund aus den vier Ecken (falls dort opak) oder Default-Weiss.
samples = []
for cx, cy in ((0, 0), (w - 1, 0), (0, h - 1), (w - 1, h - 1)):
r, g, b, a = px[cx, cy]
if a > 0:
samples.append((r, g, b))
if samples:
bg = tuple(sum(c[i] for c in samples) / len(samples) for i in range(3))
else:
bg = (248.0, 247.0, 247.0)

def dist(c):
return math.sqrt(sum((c[i] - bg[i]) ** 2 for i in range(3)))

def passable(x, y):
r, g, b, a = px[x, y]
if a == 0:
return True, 0.0
d = dist((r, g, b))
if d < bg_thresh:
return True, d
return False, None

visited = [[False] * w for _ in range(h)]
dist_map = [[0.0] * w for _ in range(h)]
q = deque()

def seed(x, y):
if not visited[y][x]:
ok, d = passable(x, y)
if ok:
visited[y][x] = True
dist_map[y][x] = d
q.append((x, y))

for x in range(w):
seed(x, 0)
seed(x, h - 1)
for y in range(h):
seed(0, y)
seed(w - 1, y)

while q:
x, y = q.popleft()
for nx, ny in ((x + 1, y), (x - 1, y), (x, y + 1), (x, y - 1)):
if 0 <= nx < w and 0 <= ny < h and not visited[ny][nx]:
ok, d = passable(nx, ny)
if ok:
visited[ny][nx] = True
dist_map[ny][nx] = d
q.append((nx, ny))

out = Image.new("RGBA", (w, h))
outpx = out.load()
feather = bg_thresh
for y in range(h):
for x in range(w):
r, g, b, a = px[x, y]
if visited[y][x]:
d = dist_map[y][x]
alpha = int(round(255 * min(1.0, d / feather)))
if alpha <= 0:
outpx[x, y] = (0, 0, 0, 0)
else:
af = alpha / 255.0
nr = (r - (1 - af) * bg[0]) / af
ng = (g - (1 - af) * bg[1]) / af
nb = (b - (1 - af) * bg[2]) / af
nr = max(0, min(255, round(nr)))
ng = max(0, min(255, round(ng)))
nb = max(0, min(255, round(nb)))
outpx[x, y] = (nr, ng, nb, alpha)
else:
outpx[x, y] = (r, g, b, 255)
out.save(out_path, "PNG")
return bg


if __name__ == "__main__":
src, dst = sys.argv[1], sys.argv[2]
thresh = float(sys.argv[3]) if len(sys.argv) > 3 else 60
used_bg = defringe(src, dst, bg_thresh=thresh)
print("bg used:", used_bg, "->", dst)
Binary file modified store_assets/Square150x150Logo.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified store_assets/Square310x310Logo.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified store_assets/Square44x44Logo.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified store_assets/StoreLogo.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified store_assets/Wide310x150Logo.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion store_package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"publisher": "CN=52596601-BAB4-4F3F-B182-E8F3F273B202",
"publisher_display": "Geiger",
"identity_name": "Geiger.ExplorerPro",
"version": "1.0.6.0",
"version": "1.0.7.0",
"description": "Lokale Explorer-Suite mit Mehrtab-Dateibrowser, Vorschau, Datenschutzprüfung, Duplikatsuche und Offline-Workflow.",
"executable": "ExplorerPro.exe",
"capabilities": "runFullTrust",
Expand Down
73 changes: 73 additions & 0 deletions tests/test_assets_and_icons.py
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,79 @@ def test_store_assets_integrity():
assert img.size == size, f"{name} size {img.size} != {size}"


def _composited_on_black_luminance(rgba_pixel):
"""Luminanz eines RGBA-Pixels, wenn er auf Schwarz kompositiert wird.
Ein teilweise opakes, aber urspruenglich helles Randpixel (unverarbeitete
Antialiasing-Uebergangsfarbe) faellt hier auf, auch wenn sein Alpha
nicht extrem hoch ist -- genau der T-20260820-729932431-Regressionsfall."""
r, g, b, a = rgba_pixel
cr, cg, cb = r * a / 255.0, g * a / 255.0, b * a / 255.0
return 0.299 * cr + 0.587 * cg + 0.114 * cb


def test_store_tiles_have_transparent_corners():
"""Regression fuer T-20260820-729932431: die vom AppxManifest referenzierten
Kacheln duerfen keinen gebackenen opaken Hintergrund haben (weisser Halo),
und der Antialiasing-Uebergang am Rand darf keinen hellen Fransensaum
hinterlassen (auf Schwarz kompositiert gemessen -- der urspruengliche Fix
liess genau ein 1px breites, zu schmal geschwellwertetes Uebergangspixel
unverarbeitet und damit sichtbar hell zurueck)."""
store_dir = REPO_ROOT / "store_assets"
manifest_tiles = [
"Square44x44Logo.png",
"Square150x150Logo.png",
"Square310x310Logo.png",
"StoreLogo.png",
]
max_ring_luminance = 200.0
for name in manifest_tiles:
with Image.open(store_dir / name).convert("RGBA") as img:
w, h = img.size
corner_alpha = img.getpixel((0, 0))[3]
assert corner_alpha < 30, (
f"{name}: Ecke (0,0) ist fast opak (Alpha {corner_alpha}) -- "
"gebackener Hintergrund statt Transparenz (weisser Halo)."
)
for y in range(h):
for x in range(w):
px = img.getpixel((x, y))
if px[3] == 0 or px[3] == 255:
continue # nicht Teil des Antialiasing-Uebergangs
lum = _composited_on_black_luminance(px)
assert lum <= max_ring_luminance, (
f"{name}: Uebergangspixel bei ({x},{y}) hat auf Schwarz "
f"kompositiert Luminanz {lum:.1f} (> {max_ring_luminance}) "
f"-- heller Fransensaum am Rand."
)

# Wide310x150Logo bettet das Motiv mittig in ein 310x150-Canvas ein;
# sowohl der aeussere Rand als auch das eingebettete Quadrat muessen
# an ihren Ecken transparent sein (der Halo im Mittelfeld war 2026-09-26
# zunaechst unentdeckt geblieben, weil nur die Aussen-Ecken geprueft wurden).
with Image.open(store_dir / "Wide310x150Logo.png").convert("RGBA") as wide:
w, h = wide.size
assert wide.getpixel((0, 0))[3] < 30, "Wide310x150Logo: aeussere Ecke nicht transparent"
square_left = (w - h) // 2
for x_offset in (2, h - 3):
for y_offset in (2, h - 3):
a = wide.getpixel((square_left + x_offset, y_offset))[3]
assert a < 30, (
f"Wide310x150Logo: Ecke des eingebetteten Quadrats bei "
f"({square_left + x_offset},{y_offset}) ist fast opak (Alpha {a})."
)
for y in range(h):
for x in range(w):
px = wide.getpixel((x, y))
if px[3] == 0 or px[3] == 255:
continue
lum = _composited_on_black_luminance(px)
assert lum <= max_ring_luminance, (
f"Wide310x150Logo: Uebergangspixel bei ({x},{y}) hat auf Schwarz "
f"kompositiert Luminanz {lum:.1f} (> {max_ring_luminance}) "
f"-- heller Fransensaum am Rand."
)


def test_app_icon_loader_returns_valid_icon():
"""Verify src.main.load_app_icon() returns a valid, non-null QIcon."""
from PySide6.QtWidgets import QApplication
Expand Down
8 changes: 4 additions & 4 deletions tests/test_metadata_contract.py
Original file line number Diff line number Diff line change
Expand Up @@ -225,7 +225,7 @@ def test_llms_txt_structure():
assert "file-bricks/ExplorerPro" in content
assert "PySide6" in content
assert "Last-checked: 2026-09-26" in content
assert "1.0.6" in content
assert "1.0.7" in content
assert "ci.yml" in content


Expand Down Expand Up @@ -267,9 +267,9 @@ def test_version_parity():
claude_text = (REPO_ROOT / "CLAUDE.md").read_text(encoding="utf-8")
changelog_text = (REPO_ROOT / "CHANGELOG.md").read_text(encoding="utf-8")

assert 'version = "1.0.6"' in pyproject_text
assert "version: 1.0.6" in claude_text
assert "## [1.0.6]" in changelog_text
assert 'version = "1.0.7"' in pyproject_text
assert "version: 1.0.7" in claude_text
assert "## [1.0.7]" in changelog_text


def test_readme_18_point_navigation_parity():
Expand Down
Loading