From 1d14019213331531e41c52ed446d5e9e0c7ffdd0 Mon Sep 17 00:00:00 2001 From: EMRG Evolution Date: Fri, 7 Aug 2026 21:33:23 +0800 Subject: [PATCH 1/3] emrg: fix locale-encoding read_text in tests (GBK Windows failures) --- tests/test_config.py | 4 ++-- tests/test_memory.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/test_config.py b/tests/test_config.py index 2abc80b0..a1dc2e77 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -102,7 +102,7 @@ def test_ensure_config_creates_if_missing(tmp_path, monkeypatch): assert not config_file.exists() ensure_config() assert config_file.exists() - content = config_file.read_text() + content = config_file.read_text(encoding="utf-8") assert "[llm]" in content assert "deepseek-chat" in content @@ -114,7 +114,7 @@ def test_ensure_config_noop_when_exists(tmp_path, monkeypatch): config_file.write_text("custom") monkeypatch.setattr("emrg.config.config_path", lambda: config_file) ensure_config() - assert config_file.read_text() == "custom" # unchanged + assert config_file.read_text(encoding="utf-8") == "custom" # unchanged def test_load_config_with_models(tmp_path, monkeypatch): diff --git a/tests/test_memory.py b/tests/test_memory.py index 3321c4d3..d6283035 100644 --- a/tests/test_memory.py +++ b/tests/test_memory.py @@ -171,7 +171,7 @@ def test_create_creates_file_and_index(self, project_store): assert file_path.exists() index_path = project_store.index_path assert index_path.exists() - assert mem.filename in index_path.read_text() + assert mem.filename in index_path.read_text(encoding="utf-8") def test_list_returns_all_active(self, project_store): project_store.create("task", "Task 1", "body1") From b0a93e193686cc89be04ccb378889216a59d8f36 Mon Sep 17 00:00:00 2001 From: EMRG Evolution Date: Fri, 7 Aug 2026 22:01:33 +0800 Subject: [PATCH 2/3] =?UTF-8?q?emrg:=20fix=20rant=20UX=20=E2=80=94=20daemo?= =?UTF-8?q?n-authoritative=20timestamp=20+=20GUI=20dialog=20textarea=20+?= =?UTF-8?q?=20lockfile?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- emrg/gui/main.js | 4 +- emrg/gui/package-lock.json | 64 +++++++++++++++++++++------- emrg/gui/renderer/css/components.css | 11 ++++- emrg/server/daemon.py | 7 ++- tests/test_daemon.py | 12 +++++- 5 files changed, 78 insertions(+), 20 deletions(-) diff --git a/emrg/gui/main.js b/emrg/gui/main.js index d6fb7c7e..eccb50e3 100644 --- a/emrg/gui/main.js +++ b/emrg/gui/main.js @@ -431,7 +431,9 @@ vision = false const frame = await client.sendCommandAndWait("rant", { message: message.trim().slice(0, 10000), project: String(project || "").trim(), - timestamp: new Date().toISOString(), + // timestamp deliberately NOT sent: daemon stamps rants with local time + // (rant 2026-08-07T13:34Z — GUI previously sent new Date().toISOString(), + // which is UTC and 8h behind on UTC+8 hosts) }, 5000); return { ok: true, count: frame.count ?? 0 }; }); diff --git a/emrg/gui/package-lock.json b/emrg/gui/package-lock.json index 8b0d9acb..bf0e05fe 100644 --- a/emrg/gui/package-lock.json +++ b/emrg/gui/package-lock.json @@ -1,12 +1,12 @@ { "name": "emrg-gui", - "version": "0.2.0", + "version": "0.2.11", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "emrg-gui", - "version": "0.2.0", + "version": "0.2.11", "dependencies": { "dompurify": "^3.4.13", "highlight.js": "^11.10.0", @@ -689,7 +689,6 @@ "integrity": "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -844,6 +843,7 @@ "integrity": "sha512-+25nxyyznAXF7Nef3y0EbBeqmGZgeN/BxHX29Rs39djAfaFalmQ89SE6CWyDCHzGL0yt/ycBtNOmGTW0FyGWNw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "archiver-utils": "^2.1.0", "async": "^3.2.4", @@ -863,6 +863,7 @@ "integrity": "sha512-bEL/yUb/fNNiNTuUz979Z0Yg5L+LzLxGJz8x79lYmR54fmTIb6ob/hNQgkQnIUDWIFjZVQwl9Xs356I6BAMHfw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "glob": "^7.1.4", "graceful-fs": "^4.2.0", @@ -885,6 +886,7 @@ "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", @@ -900,7 +902,8 @@ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/archiver-utils/node_modules/string_decoder": { "version": "1.1.1", @@ -908,6 +911,7 @@ "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "safe-buffer": "~5.1.0" } @@ -1009,6 +1013,7 @@ "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "buffer": "^5.5.0", "inherits": "^2.0.4", @@ -1381,6 +1386,7 @@ "integrity": "sha512-D3uMHtGc/fcO1Gt1/L7i1e33VOvD4A9hfQLP+6ewd+BvG/gQ84Yh4oftEhAdjSMgBgwGL+jsppT7JYNpo6MHHg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "buffer-crc32": "^0.2.13", "crc32-stream": "^4.0.2", @@ -1481,6 +1487,7 @@ "integrity": "sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==", "dev": true, "license": "Apache-2.0", + "peer": true, "bin": { "crc32": "bin/crc32.njs" }, @@ -1494,6 +1501,7 @@ "integrity": "sha512-NT7w2JVU7DFroFdYkeq8cywxrgjPHWkdX1wjpRQXPX5Asews3tA+Ght6lddQO5Mkumffp3X7GEqku3epj2toIw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "crc-32": "^1.2.0", "readable-stream": "^3.4.0" @@ -1671,7 +1679,6 @@ "integrity": "sha512-rcJUkMfnJpfCboZoOOPf4L29TRtEieHNOeAbYPWPxlaBw/Z1RKrRA86dOI9rwaI4tQSc/RD82zTNHprfUHXsoQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "app-builder-lib": "24.13.3", "builder-util": "24.13.1", @@ -1865,6 +1872,7 @@ "integrity": "sha512-oHkV0iogWfyK+ah9ZIvMDpei1m9ZRpdXcvde1wTpra2U8AFDNNpqJdnin5z+PM1GbQ5BoaKCWas2HSjtR0HwMg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "app-builder-lib": "24.13.3", "archiver": "^5.3.1", @@ -1878,6 +1886,7 @@ "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", @@ -1893,6 +1902,7 @@ "integrity": "sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "universalify": "^2.0.0" }, @@ -1906,6 +1916,7 @@ "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">= 10.0.0" } @@ -2222,7 +2233,8 @@ "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/fs-extra": { "version": "8.1.0", @@ -2727,7 +2739,8 @@ "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/isbinaryfile": { "version": "5.0.7", @@ -2874,6 +2887,7 @@ "integrity": "sha512-b94GiNHQNy6JNTrt5w6zNyffMrNkXZb3KTkCZJb2V1xaEGCk093vkZ2jk3tpaeP33/OiXC+WvK9AxUebnf5nbw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "readable-stream": "^2.0.5" }, @@ -2887,6 +2901,7 @@ "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", @@ -2902,7 +2917,8 @@ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/lazystream/node_modules/string_decoder": { "version": "1.1.1", @@ -2910,6 +2926,7 @@ "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "safe-buffer": "~5.1.0" } @@ -2926,35 +2943,40 @@ "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz", "integrity": "sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/lodash.difference": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/lodash.difference/-/lodash.difference-4.5.0.tgz", "integrity": "sha512-dS2j+W26TQ7taQBGN8Lbbq04ssV3emRw4NY58WErlTO29pIqS0HmoT5aJ9+TUQ1N3G+JOZSji4eugsWwGp9yPA==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/lodash.flatten": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/lodash.flatten/-/lodash.flatten-4.4.0.tgz", "integrity": "sha512-C5N2Z3DgnnKr0LOpv/hKCgKdb7ZZwafIrsesve6lmzvZIRZRGaZ/l6Q8+2W7NaT+ZwO3fFlSCzCzrDCFdJfZ4g==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/lodash.isplainobject": { "version": "4.0.6", "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/lodash.union": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/lodash.union/-/lodash.union-4.6.0.tgz", "integrity": "sha512-c4pB2CdGrGdjMKYLA+XiRDO7Y0PRQbm/Gzg8qMj+QH+pFVAoTp5sBpO0odL3FjoPCGjK96p6qsP+yQoiLoOBcw==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/lowercase-keys": { "version": "2.0.0", @@ -3155,6 +3177,7 @@ "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=0.10.0" } @@ -3288,7 +3311,8 @@ "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/progress": { "version": "2.0.3", @@ -3372,6 +3396,7 @@ "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", @@ -3387,6 +3412,7 @@ "integrity": "sha512-v05I2k7xN8zXvPD9N+z/uhXPaj0sUFCe2rcWZIpBsqxfP7xXFQ0tipAd/wjj1YxWyWtUS5IDJpOG82JKt2EAVA==", "dev": true, "license": "Apache-2.0", + "peer": true, "dependencies": { "minimatch": "^5.1.0" } @@ -3469,7 +3495,8 @@ "url": "https://feross.org/support" } ], - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/safer-buffer": { "version": "2.1.2", @@ -3680,6 +3707,7 @@ "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "safe-buffer": "~5.2.0" } @@ -3793,6 +3821,7 @@ "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "bl": "^4.0.3", "end-of-stream": "^1.4.1", @@ -3950,7 +3979,8 @@ "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/verror": { "version": "1.10.1", @@ -4122,6 +4152,7 @@ "integrity": "sha512-9qv4rlDiopXg4E69k+vMHjNN63YFMe9sZMrdlvKnCjlCRWeCBswPPMPUfx+ipsAWq1LXHe70RcbaHdJJpS6hyQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "archiver-utils": "^3.0.4", "compress-commons": "^4.1.2", @@ -4137,6 +4168,7 @@ "integrity": "sha512-KVgf4XQVrTjhyWmx6cte4RxonPLR9onExufI1jhvw/MQ4BB6IsZD5gT8Lq+u/+pRkWna/6JoHpiQioaqFP5Rzw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "glob": "^7.2.3", "graceful-fs": "^4.2.0", diff --git a/emrg/gui/renderer/css/components.css b/emrg/gui/renderer/css/components.css index 16ff50ca..d6bc01c2 100644 --- a/emrg/gui/renderer/css/components.css +++ b/emrg/gui/renderer/css/components.css @@ -560,11 +560,20 @@ dialog::backdrop { margin-bottom: var(--sp-3); } .dialog-card label > input, -.dialog-card label > select { +.dialog-card label > select, +.dialog-card label > textarea { width: 100%; margin-top: var(--sp-1); padding: 8px 10px; } +/* rant 2026-08-07T13:32Z: rant dialog textarea was unstyled — default box was + tiny. Full width + comfortable min-height + vertical resize + inherited font. */ +.dialog-card label > textarea { + min-height: 120px; + resize: vertical; + font: inherit; + line-height: 1.5; +} .dialog-card .hint { font-size: var(--fs-aux); color: var(--text-3); diff --git a/emrg/server/daemon.py b/emrg/server/daemon.py index c0580e67..64dfa574 100644 --- a/emrg/server/daemon.py +++ b/emrg/server/daemon.py @@ -1083,8 +1083,13 @@ async def _process_message( # Field order: timestamp → project → status → progress → completed → message # (project right after timestamp per user feedback; message last) + # Timestamp is daemon-authoritative local time (rant 2026-08-07T13:34Z): + # clients previously supplied timestamps — GUI sent new Date().toISOString() + # (UTC, 8h behind on UTC+8 hosts), TUI sent naive local time. A tz-aware + # local ISO timestamp (+08:00) is self-describing, sorts correctly, and is + # consistent regardless of which client submitted the rant. entry = { - "timestamp": msg.get("timestamp", datetime.now().isoformat()), + "timestamp": datetime.now().astimezone().isoformat(), "project": project, "status": "pending", "progress": None, diff --git a/tests/test_daemon.py b/tests/test_daemon.py index 920756b5..34b758fe 100644 --- a/tests/test_daemon.py +++ b/tests/test_daemon.py @@ -502,7 +502,10 @@ def test_rant_field_order(tmp_path, monkeypatch): "type": "rant", "message": "test rant message", "project": "emrg", - "timestamp": "2026-07-31T20:46:59.734987", + # Client-supplied timestamp must be IGNORED — the daemon stamps rants + # with tz-aware local time (rant 2026-08-07T13:34Z: GUI sent UTC Z, + # 8h behind on UTC+8 hosts). A stale UTC value here must not leak through. + "timestamp": "2026-07-31T20:46:59.734987Z", }, writer)) # type: ignore[arg-type] lines = (tmp_path / "rants.jsonl").read_text(encoding="utf-8").strip().splitlines() @@ -515,6 +518,13 @@ def test_rant_field_order(tmp_path, monkeypatch): assert entry["project"] == "emrg" assert entry["status"] == "pending" assert entry["message"] == "test rant message" + # Daemon-authoritative local timestamp: tz-aware, NOT UTC (no Z suffix), + # and within 60s of the wall clock. + import datetime as _dt + assert not entry["timestamp"].endswith("Z") + ts = _dt.datetime.fromisoformat(entry["timestamp"]) + assert ts.tzinfo is not None + assert abs((_dt.datetime.now(ts.tzinfo) - ts).total_seconds()) < 60 # ── _redact 日志脱敏(rant 10:21 + 跨项目 base64 教训)────────────── From 444e5140523d35a5a2ca090db344c5941ef59f6d Mon Sep 17 00:00:00 2001 From: EMRG Evolution Date: Fri, 7 Aug 2026 22:03:41 +0800 Subject: [PATCH 3/3] emrg: force UTF-8 encoding on emrgd.log RotatingFileHandler --- emrg/server/__main__.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/emrg/server/__main__.py b/emrg/server/__main__.py index 65d00608..f6229a89 100644 --- a/emrg/server/__main__.py +++ b/emrg/server/__main__.py @@ -15,7 +15,8 @@ datefmt="%H:%M:%S", handlers=[ RotatingFileHandler( - str(_log_file), maxBytes=10 * 1024 * 1024, backupCount=3 + str(_log_file), maxBytes=10 * 1024 * 1024, backupCount=3, + encoding="utf-8", # rant 2026-08-07T14:00Z: default locale code page (GBK on zh-CN Windows) mojibakes CJK log lines ), logging.StreamHandler(), # also to stderr (visible when run directly) ],