v0.2.19 : preset actif de nouveau détecté (empreinte ignore les clés appareil MEM_MODE/CRAWL4AI_URL) + surbrillance bleue du preset actif

This commit is contained in:
nathaninline committed 2026-07-10 09:49:06 +02:00
1 parent 20dd09abc6
commit acb08d3c6a
6 files changed
+48 -13

No files matched your search

+1 -1
View File
@@ -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/<nom>/SKILL.md) vers la
+40 -5
View File
@@ -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 })
Binary file not shown.
Binary file not shown.
+4 -4
View File
@@ -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}
+3 -3
View File
@@ -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": {