diff --git a/main.go b/main.go index 57ccfdd..94f4373 100644 --- a/main.go +++ b/main.go @@ -16,7 +16,7 @@ import ( "strings" ) -const Version = "0.2.18" +const Version = "0.2.19" func main() { // Migration one-shot des anciens skills (SKILLS//SKILL.md) vers la diff --git a/presets.go b/presets.go index ed88179..44fa67d 100644 --- a/presets.go +++ b/presets.go @@ -51,15 +51,51 @@ func withDisplayName(content, name string) string { return line + "\n" + content } +// presetFingerprint hashes config content while IGNORING the device-level keys +// (preservedKeys) that SwitchToPreset re-applies on top of a freshly copied +// preset. Without this, those injected MEM_MODE/CRAWL4AI_URL lines make +// config.env differ from every preset file, so no preset is ever detected as +// active (regression introduced when preservedKeys were added). We strip those +// assignment lines symmetrically from both sides before hashing so a plain +// content match still identifies the active preset. +func presetFingerprint(content []byte) string { + var b strings.Builder + for _, line := range strings.Split(string(content), "\n") { + if isPreservedAssignment(line) { + continue + } + b.WriteString(line) + b.WriteByte('\n') + } + h := sha1.Sum([]byte(b.String())) + return hex.EncodeToString(h[:]) +} + +// isPreservedAssignment reports whether a line assigns one of the preservedKeys +// (commented or not), e.g. `MEM_MODE=off` or `# CRAWL4AI_URL=...`. +func isPreservedAssignment(line string) bool { + t := strings.TrimSpace(strings.TrimPrefix(strings.TrimSpace(line), "#")) + eq := strings.IndexByte(t, '=') + if eq < 0 { + return false + } + key := strings.TrimSpace(t[:eq]) + for _, k := range preservedKeys { + if key == k { + return true + } + } + return false +} + // ListPresets returns all configs/*.env, marking the one whose contents match -// the current config.env (by SHA-1), sorted by display name. +// the current config.env (ignoring device-level preservedKeys), sorted by name. func ListPresets() ([]Preset, error) { dir := presetsDir() _ = os.MkdirAll(dir, 0o755) cur := "" if b, err := os.ReadFile(confPath()); err == nil { - h := sha1.Sum(b) - cur = hex.EncodeToString(h[:]) + cur = presetFingerprint(b) } entries, err := os.ReadDir(dir) if err != nil { @@ -75,13 +111,12 @@ func ListPresets() ([]Preset, error) { if err != nil { continue } - h := sha1.Sum(b) id := strings.TrimSuffix(e.Name(), ".env") out = append(out, Preset{ ID: id, Name: presetDisplayName(string(b), id), Path: p, - Active: hex.EncodeToString(h[:]) == cur, + Active: presetFingerprint(b) == cur, }) } sort.Slice(out, func(i, j int) bool { return out[i].Name < out[j].Name }) diff --git a/resource_windows_amd64.syso b/resource_windows_amd64.syso index f1903fd..fc5dc40 100644 Binary files a/resource_windows_amd64.syso and b/resource_windows_amd64.syso differ diff --git a/resource_windows_arm64.syso b/resource_windows_arm64.syso index 6ab01a8..d758a2c 100644 Binary files a/resource_windows_arm64.syso and b/resource_windows_arm64.syso differ diff --git a/ui/index.html b/ui/index.html index 8fad618..1f60548 100644 --- a/ui/index.html +++ b/ui/index.html @@ -21,9 +21,9 @@ thème plus tard : 1) copier un bloc [data-theme="…"] ci-dessous en changeant les valeurs, 2) ajouter son {id,label} au registre THEMES (voir le JS). Le reste (sélecteur, persistance, application) est automatique. */ -:root{color-scheme:dark;--bg:#0d1117;--panel:#161b22;--border:#30363d;--text:#e6edf3;--dim:#7d8590;--accent:#58a6ff;--ok:#58a6ff;--warn:#d29922;--err:#f85149;--mag:#bc8cff;--code-bg:#0d1117;--danger-bg:#3d1f23;--warn-bg:#3d2f1f} -[data-theme="dark"]{color-scheme:dark;--bg:#0d1117;--panel:#161b22;--border:#30363d;--text:#e6edf3;--dim:#7d8590;--accent:#58a6ff;--ok:#58a6ff;--warn:#d29922;--err:#f85149;--mag:#bc8cff;--code-bg:#0d1117;--danger-bg:#3d1f23;--warn-bg:#3d2f1f} -[data-theme="light"]{color-scheme:light;--bg:#f0f1f4;--panel:#f8f9fb;--border:#d7dbe1;--text:#24292f;--dim:#636b74;--accent:#0969da;--ok:#0969da;--warn:#9a6700;--err:#cf222e;--mag:#8250df;--code-bg:#e6e9ee;--danger-bg:#fbe6e6;--warn-bg:#fbefda} +:root{color-scheme:dark;--bg:#0d1117;--panel:#161b22;--border:#30363d;--text:#e6edf3;--dim:#7d8590;--accent:#58a6ff;--accent-bg:rgba(88,166,255,.12);--ok:#58a6ff;--warn:#d29922;--err:#f85149;--mag:#bc8cff;--code-bg:#0d1117;--danger-bg:#3d1f23;--warn-bg:#3d2f1f} +[data-theme="dark"]{color-scheme:dark;--bg:#0d1117;--panel:#161b22;--border:#30363d;--text:#e6edf3;--dim:#7d8590;--accent:#58a6ff;--accent-bg:rgba(88,166,255,.12);--ok:#58a6ff;--warn:#d29922;--err:#f85149;--mag:#bc8cff;--code-bg:#0d1117;--danger-bg:#3d1f23;--warn-bg:#3d2f1f} +[data-theme="light"]{color-scheme:light;--bg:#f0f1f4;--panel:#f8f9fb;--border:#d7dbe1;--text:#24292f;--dim:#636b74;--accent:#0969da;--accent-bg:rgba(9,105,218,.10);--ok:#0969da;--warn:#9a6700;--err:#cf222e;--mag:#8250df;--code-bg:#e6e9ee;--danger-bg:#fbe6e6;--warn-bg:#fbefda} *{box-sizing:border-box} body{margin:0;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;background:var(--bg);color:var(--text);height:100vh;height:100dvh;display:flex;font-size:14px} /* PWA plein écran (viewport-fit=cover) : on décale le contenu des bords pour ne @@ -134,7 +134,7 @@ button:disabled{opacity:.5;cursor:not-allowed} .row{display:flex;gap:4px;flex-wrap:wrap} .preset{display:flex;align-items:center;justify-content:space-between;gap:6px;padding:6px 10px;border:1px solid var(--border);border-radius:6px;margin:4px 0;cursor:pointer;background:var(--panel)} .preset:hover{border-color:var(--accent)} -.preset.active{border-color:var(--ok);color:var(--ok)} +.preset.active{border-color:var(--accent);color:var(--accent);border-width:1.5px;padding:5.5px 9.5px} .preset>span{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap} .preset>button{flex-shrink:0} .preset-info{flex:1;min-width:0} diff --git a/versioninfo.json b/versioninfo.json index 5328b42..12b4d0b 100644 --- a/versioninfo.json +++ b/versioninfo.json @@ -3,13 +3,13 @@ "FileVersion": { "Major": 0, "Minor": 2, - "Patch": 18, + "Patch": 19, "Build": 0 }, "ProductVersion": { "Major": 0, "Minor": 2, - "Patch": 18, + "Patch": 19, "Build": 0 }, "FileFlagsMask": "3f", @@ -25,7 +25,7 @@ "LegalCopyright": "Copyright (c) 2026 Jean contributors. MIT License.", "OriginalFilename": "jean.exe", "ProductName": "Jean", - "ProductVersion": "0.2.18", + "ProductVersion": "0.2.19", "Comments": "https://github.com/nathaninline/jean — projet open source (MIT)" }, "VarFileInfo": {