From f2c1265a1c2ace3b017ccd0a0a9d6dfa359d78a0 Mon Sep 17 00:00:00 2001 From: Wyndham ARR Date: Tue, 11 Aug 2026 17:23:33 +0800 Subject: [PATCH] fix: make daily task log header-only --- .project-docs/00-brief/success-criteria.md | 3 +- .project-docs/30-worklog/current-state.md | 52 ++++++++++++++++--- .project-docs/30-worklog/task-history.md | 7 ++- .project-docs/50-evidence/evidence-index.md | 5 +- ...026-07-30-task-log-relocation-and-scope.md | 5 +- ...6-08-06-daily-review-ui-navigation-i18n.md | 4 ++ ...6-08-11-0806-workbook-to-res-detail-xml.md | 32 ++++++++++++ .../2026-08-11-0806-xml-invalid-utf8.md | 41 +++++++++++++++ ...08-11-daily-row-task-log-entry-boundary.md | 47 +++++++++++++++++ ...l-surfaces-need-explicit-entry-controls.md | 39 ++++++++++++++ .../60-reflection/reflection-index.md | 1 + .project-docs/90-maintenance/stale-items.md | 3 +- arr_web/static/app.js | 17 +----- arr_web/static/styles.css | 4 -- tests/test_arr_web_daily_price_review_ui.py | 4 +- tests/test_arr_web_task_log_ui.py | 21 +++++++- 16 files changed, 251 insertions(+), 34 deletions(-) create mode 100644 .project-docs/50-evidence/topics/2026-08-11-0806-workbook-to-res-detail-xml.md create mode 100644 .project-docs/50-evidence/topics/2026-08-11-0806-xml-invalid-utf8.md create mode 100644 .project-docs/50-evidence/topics/2026-08-11-daily-row-task-log-entry-boundary.md create mode 100644 .project-docs/60-reflection/cases/2026-08-11-technical-surfaces-need-explicit-entry-controls.md diff --git a/.project-docs/00-brief/success-criteria.md b/.project-docs/00-brief/success-criteria.md index 31cf2d4..f62da97 100644 --- a/.project-docs/00-brief/success-criteria.md +++ b/.project-docs/00-brief/success-criteria.md @@ -20,6 +20,7 @@ - 测试必须证明月报触发发生在数据库成功提交之后,且失败提交不会触发。 - 测试必须覆盖合并重复价格键、候选价、整数格式、显式 `0`(冻结清单规范化为 `0.00`)、未完成禁止确认、revision 冲突、重复最终化、技术重试、取消留痕、认证/CSRF和隐私字段隔离。 - `待人工处理` 状态与复核进度必须作为同一个操作入口,点击或键盘激活后定位并聚焦人工价格面板,不能误开任务日志;复核面板、动态消息、确认弹窗和安全 API 错误必须完整支持中文、英文和泰文。 +- Daily Report 具体行必须是非操作型表格内容,不得通过鼠标、Enter 或 Space 打开任务日志;只有页头右上角的 `任务日志` 控件可以打开 `#task-log-dialog`,下载和人工复核仍由各自明确控件触发。 - 人工确认请求在途时,上传卡片必须显示本地化的“日报生成中”;请求结束后按成功、仍在生成或可重试复核状态收敛,不能继续显示过期的“等待人工定价”。 - 最终人工成功必须证明日报行序/数值可重放、Finance 只有一个 active 版本、`manual_review` 行数和冻结清单 SHA-256 可追踪、月报事件仅一次。 - 重新打开生成的 XLSX,确认 `TOTAL PRICE` 为公式单元格并逐行引用同一行的 `REAL PRICE`、`NIGHTS`、`NO_OF_ROOMS`。 @@ -28,4 +29,4 @@ ## Last Reviewed -2026-08-06 +2026-08-11 diff --git a/.project-docs/30-worklog/current-state.md b/.project-docs/30-worklog/current-state.md index 1e0d069..206f3c1 100644 --- a/.project-docs/30-worklog/current-state.md +++ b/.project-docs/30-worklog/current-state.md @@ -19,6 +19,17 @@ preserves its no-formula contract. Both publishers upload new workbook and `resu immutable OSS adapter, while download routing retains controlled-local compatibility for historical records and keeps `.web-jobs` queue state local. +## Daily Interaction Correction On 2026-08-11 + +- The local source now treats Daily Report rows as informational table rows rather than hidden log controls. It removes + row click and Enter/Space activation, `role=button`, `tabindex`, selected-row decoration and pointer styling. The + explicit needs-review button still focuses the manual-price panel, report downloads remain links, and only the + top-right `任务日志` link opens the trace dialog. +- JavaScript syntax, all 81 `test_arr_web*.py` regressions and an isolated browser interaction check pass. The browser + proved an ordinary `0805.XML` row leaves the dialog closed, the `待人工处理` control focuses a populated review panel, + and the header utility opens the dialog; there were no console errors. This source change has not been pushed or + deployed to `8.138.234.141:8765`, so the reported production behavior remains until a controlled release. + ## Implemented Locally On 2026-08-11 - Implemented Booking parser 2.1.0 exact-header compatibility for the user-confirmed July legacy and August bilingual @@ -31,6 +42,34 @@ immutable OSS adapter, while download routing retains controlled-local compatibi draft, Booking batch, Finance fact or runtime process was changed. Deployment and a controlled post-restart read-only replay remain pending; QBD pending items are existing room-type review work, not a header failure. +## Diagnosed On 2026-08-11 + +- The reported company-channel Booking Excel parse failure occurs before object storage and draft persistence; a + read-only database snapshot shows no new draft/artifact/batch and accepted batch 7 `july-test.xlsx` remains current. + Exact parser replay isolates two cases: generated Daily/channel report workbooks are correctly rejected as the wrong + input type, while current Lian Tai/QBD raw workbooks expose a parser compatibility defect because bilingual + `Tour Code / 主团号` and hotel-detail headers do not equal the narrow exact alias allowlist. Replacing only those two + headers in memory makes both raw workbooks parse, with original files and all business state unchanged. At diagnosis, + the controlled alias expansion plus regression fixtures remained unimplemented. The user has now explicitly confirmed that both + supplied formats must be supported. An exact three-alias process-memory probe recognizes only the intended sheet in + each file: Lian Tai yields 67 rows/100 items/16 pending, while QBD 2026-08-06 yields 104 rows/218 items/212 pending; + Daily/channel output workbooks remain rejected. This parser-only plan needs no migration or upload-page redesign, but + the existing one-file/full-replacement activation model still cannot append two separately uploaded files. +- The exact desktop `0806.XML` deterministically fails processor 4.0.0 before any row or price processing with + `XML_PARSE_ERROR` at line 1872, column 0. A `TRACE_TEXT` value contains one CESU-8 surrogate pair representing + `U+1F389` even though the document declares UTF-8; the file is structurally closed and not truncated. This error is + outside the pure-`PRICE_UNMATCHED` review boundary, so a formal failure with no manual-price panel is correct. The + public deployment health endpoint remains 200. The source must be re-exported as valid UTF-8 and uploaded as a new + task; no source, runtime, database, Finance, object-store or event state was changed during diagnosis. + +## Artifact Conversion On 2026-08-11 + +- The user-supplied `8.6 修正.XML` was an XLSX package mislabeled with an XML extension. Its 181-row first sheet + contains the flattened Opera fields needed by the ARR parser. A separate UTF-8 `RES_DETAIL` XML was reconstructed + from those rows and passed strict XML/UTF-8 checks plus processor 4.0.0: 115 retained rows, 37 rate-code exclusions, + 29 duplicates and zero price-unmatched/review rows. The original workbook and malformed XML remain unchanged; no + upload, Finance, database, object-store or event state was changed. + ## Runtime Recovery On 2026-08-06 - Restored the local ARR login page after a reported opening failure. No Web process was listening and the retained @@ -315,8 +354,9 @@ immutable OSS adapter, while download routing retains controlled-local compatibi - Updated the daily XML upload surface so submitting ARR.XML stays on the daily page instead of opening the task-log dialog automatically. The upload card now shows a compact stage-based estimate for upload, fixed processing, validation and database commit, finalizing at 100% on success and retaining an inline error state on failure. The - header task-log button and daily-history row actions still open the existing trace dialog on demand. JavaScript - syntax and 15 focused Web/static/trace tests pass; no API or processing behavior changed. + header task-log button remains the sole on-demand trace entry; the later 2026-08-11 interaction correction removed + daily-history row log activation. JavaScript syntax and the original 15 focused Web/static/trace tests passed; no API + or processing behavior changed. - Simplified the desktop monthly page into a single six-column download table. The redundant standard-monthly heading, `VERSION HISTORY`/version-record copy, visible monthly `版本` column and C/O-period footer note are removed; the navigation label is now `月报`; the list is identified by a compact `月报处理` heading. The visible `更新至` cell reads the explicit `max_arrival_date` API field, which is @@ -401,10 +441,10 @@ immutable OSS adapter, while download routing retains controlled-local compatibi - The desktop and H5 headers use the plain `ARR Report` identity with no decorative icon. The daily content heading is `Daily Report`; the duplicated daily/monthly explanatory copy has been removed without changing automatic publishing. - The desktop header utility formerly labeled `手机看板` is now `任务日志` and opens the existing black task console in - a native modal. The console no longer occupies the daily-processing layout; upload completion and daily-history row - activation open the selected job directly. `/h5` remains available by direct URL. Both history and trace SQL are - explicitly limited to `pipeline_type = 'opera_daily'`, so this is a one-daily-job processing trace rather than a - global server, monthly-run or company-report log. + a native modal. The console no longer occupies the daily-processing layout, and the 2026-08-11 interaction correction + makes this header utility its only opener; upload completion and Daily rows do not open it. `/h5` remains available + by direct URL. Both history and trace SQL are explicitly limited to `pipeline_type = 'opera_daily'`, so this is a + one-daily-job processing trace rather than a global server, monthly-run or company-report log. - Refined the daily desktop surface after visual review. `ARR Report` remains the dominant workspace title while `Daily Report` is smaller; `任务日志` is a compact outlined button at the adjacent status-label size; the upload station is centered and responsive; decorative `01 / UPLOAD` and `本月留痕` labels are gone; and `开始处理` is a diff --git a/.project-docs/30-worklog/task-history.md b/.project-docs/30-worklog/task-history.md index 177143c..473850e 100644 --- a/.project-docs/30-worklog/task-history.md +++ b/.project-docs/30-worklog/task-history.md @@ -5,7 +5,12 @@ | Date | Task | Outcome | Docs Updated | |---|---|---|---| | 2026-08-11 | Implement Lian Tai/QBD bilingual Booking-header compatibility | Bumped the bounded parser to 2.1.0, converted readable approved header labels into the existing exact normalized allowlist, preserved legacy aliases and fail-closed ambiguity, and added Chinese/English/Thai header-error mapping. Synthetic parser/coordinator tests, all four supplied workbook replays and wrong-report negatives pass; no API, migration, upload, draft, source activation or runtime deployment occurred | README, architecture/data-flow/business rules, current state, [header-parse evidence](../50-evidence/topics/2026-08-11-company-channel-booking-header-parse-failure.md), evidence index, stale deployment item | -| 2026-08-06 | Repair manual-review navigation/progress and add English/Thai | Merged the status chip and progress into one review action that focuses the operation panel while row clicks still own logs; removed the frozen-case sentence; set upload status to `日报生成中` before finalization; localized the complete review UI and review API errors. Live browser QA covered zh/en/th and dialog copy without submitting a business action. Full suite: 393 passed, 3 intentional skips | Current state, success criteria, module map, [UI evidence](../50-evidence/topics/2026-08-06-daily-review-ui-navigation-i18n.md), scoped planning record | +| 2026-08-11 | Make the top-right control the sole Daily task-log entry | Removed concrete-row click/Enter/Space log activation plus the row's button semantics, pointer, focus and selected styling. Explicit download and manual-review controls remain; isolated browser QA proved ordinary row → no dialog, review control → focused operation panel, header `任务日志` → dialog. JavaScript syntax and all 81 Web tests pass. The source is local and production deployment is still pending | Success criteria, current state, [interaction evidence](../50-evidence/topics/2026-08-11-daily-row-task-log-entry-boundary.md), evidence index, stale deployment item, scoped planning record | +| 2026-08-11 | Plan Lian Tai/QBD bilingual Booking-header compatibility | Verified the exact supplied header cells and rendered header-only ranges. Current parser 2.0.0 rejects both; an in-memory exact three-alias expansion parses only the intended sheets (Lian Tai 67/100/16 pending; QBD 104/218/212 pending) while two report-output negatives remain rejected. Recommended parser 2.1.0, synthetic parser/coordinator regressions, neutral error copy and docs; no migration/UI redesign. One-file/full-replacement activation remains a separate operational boundary. No code, workbook, database or runtime changed | Current state, [header-parse evidence](../50-evidence/topics/2026-08-11-company-channel-booking-header-parse-failure.md), evidence index, stale item, scoped planning record | +| 2026-08-11 | Convert user-supplied `8.6 修正.XML` into an uploadable XML | Identified the input as an XLSX package mislabeled `.XML`; reconstructed a UTF-8 `RES_DETAIL` XML from its 181 flattened rows and validated it with `xmllint`, strict UTF-8 decoding and processor 4.0.0. Result: 115 retained rows, 37 exclusions, 29 duplicates, zero price-unmatched/review rows. Original workbook and malformed XML were left unchanged; no business/database write occurred | Current state, [conversion evidence](../50-evidence/topics/2026-08-11-0806-workbook-to-res-detail-xml.md), evidence index | +| 2026-08-11 | Diagnose “公司渠道明细” Excel parse failure | Read-only database evidence proves the request stopped before draft/artifact/source persistence and left accepted batch 7 current. Exact production-parser replay shows generated report workbooks are correctly rejected, while current Lian Tai/QBD raw workbooks fail because their bilingual Tour Code and hotel-detail headers do not equal the exact parser aliases; changing only both headers in memory makes both parse. No code, workbook, database or runtime state changed | Current state, [header-parse evidence](../50-evidence/topics/2026-08-11-company-channel-booking-header-parse-failure.md), evidence index, stale item | +| 2026-08-11 | Diagnose failed `0806.XML` | Public health remained 200, while an isolated processor-4.0.0 replay of the exact desktop file failed before parsing any row. Line 1872 inside `TRACE_TEXT` contains one CESU-8 surrogate pair for `U+1F389` although the file declares UTF-8, producing `XML_PARSE_ERROR`; therefore the run correctly cannot enter price review. No source, upload, runtime or business state changed | Current state, [invalid-UTF-8 evidence](../50-evidence/topics/2026-08-11-0806-xml-invalid-utf8.md), evidence index | +| 2026-08-06 | Repair manual-review navigation/progress and add English/Thai | Merged the status chip and progress into one review action that focuses the operation panel; at that time ordinary row clicks still owned logs, which the 2026-08-11 entry-boundary correction supersedes. Removed the frozen-case sentence, set upload status to `日报生成中` before finalization and localized the complete review UI/API errors. Live browser QA covered zh/en/th without a business mutation. Full suite: 393 passed, 3 intentional skips | Current state, success criteria, module map, [UI evidence](../50-evidence/topics/2026-08-06-daily-review-ui-navigation-i18n.md), scoped planning record | | 2026-08-06 | Use integer review prices and repair `请求未完成` finalization | Changed review display/PATCH input to non-negative integer text while retaining canonical `.00` storage and replay. Bound the visible failure to runs 66/67, repaired a 21-placeholder/20-value Finance INSERT and the missing `manual_override_json` artifact kind through additive migration 018, then proved the full PostgreSQL finalization path in an outer rollback. Web PID 26286 is healthy; latest run 67 remains explicitly retryable with frozen `200/0` and no Finance/outbox mutation. Full suite: 387 passed, 3 intentional skips | ADR/domain/architecture/current state, migration/deployment ledgers, [repair evidence](../50-evidence/topics/2026-08-06-integer-review-finalize-repair.md), scoped planning record | | 2026-08-06 | Deploy migration 017 and restore live `0805.XML` manual review | Reconciled already-live 016 semantics, corrected a legacy run-constraint drift found by rollback probe, formally applied 017 with unchanged business counts, closed runs 61–63 as infrastructure failures, then fixed a 16-value/15-placeholder delivery INSERT exposed by the first acceptance upload. Restarted Web and proved run 65 opens the exact two-key review panel at revision 0 with no prices, daily XLSX, Finance version or daily-commit/monthly event. Historical version 28 remains rejected | Migration ledger, current state, [live rollout evidence](../50-evidence/topics/2026-08-06-live-daily-price-review-rollout.md), SHA-protected runtime checkpoints, scoped planning record | | 2026-08-06 | Diagnose fresh `0805.XML` v4 upload not entering review | Proved the exact file correctly yields independently valid v4 `review_required` with two keys, but live `booking_test` lacks migration 017 tables/states. Three new runs remain `running` after source registration because review commit and best-effort closure both depend on the missing table; no delivery, Finance or outbox fact exists. Live 016 effects are present despite the checked-in ledger saying pending. No database/runtime/business mutation was performed; 017 deployment and explicit shell cleanup require operator authorization | Current state, [live mismatch evidence](../50-evidence/topics/2026-08-06-live-0805-review-migration-mismatch.md), evidence index, stale item, scoped planning record | diff --git a/.project-docs/50-evidence/evidence-index.md b/.project-docs/50-evidence/evidence-index.md index 0f590c3..14a0307 100644 --- a/.project-docs/50-evidence/evidence-index.md +++ b/.project-docs/50-evidence/evidence-index.md @@ -4,7 +4,10 @@ Use this index for searchable, traceable evidence records. | Date | Topic | Status | Source | Detail | |---|---|---|---|---| +| 2026-08-11 | Daily row / task-log entry boundary | Implemented locally; production deployment pending | [Evidence topic](topics/2026-08-11-daily-row-task-log-entry-boundary.md) | Daily rows no longer have log-opening click/keyboard handlers, button semantics or interactive styling. Manual-review and download controls remain explicit, and the header `任务日志` link is the sole dialog opener. JavaScript syntax, 81 Web tests and isolated browser interaction QA pass with no console errors; no production or business state changed. | +| 2026-08-11 | Convert `8.6 修正.XML` workbook to uploadable XML | Converted and independently accepted | [Evidence topic](topics/2026-08-11-0806-workbook-to-res-detail-xml.md) | The supplied `.XML` is actually an XLSX with 181 rows and flattened `RES_DETAIL` fields. A new UTF-8 `RES_DETAIL` XML was reconstructed without changing the workbook; strict XML/UTF-8 checks and processor 4.0.0 pass with 115 retained rows, zero price-unmatched rows and no business/database write. | | 2026-08-11 | Company-channel Booking Excel parse failure | Parser 2.1.0 implemented locally; deployment pending | [Evidence topic](topics/2026-08-11-company-channel-booking-header-parse-failure.md) | The user confirmed both supplied formats must be accepted. Parser 2.1.0 preserves exact whole-cell matching while accepting the three approved bilingual aliases; synthetic/coordinator tests, four supplied workbook replays and wrong-report negatives pass. Separate-file append remains outside the singleton full-source model. | +| 2026-08-11 | `0806.XML` invalid UTF-8 | Diagnosed read-only; source re-export required | [Evidence topic](topics/2026-08-11-0806-xml-invalid-utf8.md) | Processor 4.0.0 fails before row parsing with `XML_PARSE_ERROR` at line 1872, column 0. A `TRACE_TEXT` value contains one CESU-8 surrogate pair for `U+1F389` despite the UTF-8 declaration. Because this is not pure `PRICE_UNMATCHED`, failure rather than manual review is correct. The public service remains healthy and no business state changed. | | 2026-08-06 | Daily review navigation, progress and three-language UI | Implemented and runtime-active | [Evidence topic](topics/2026-08-06-daily-review-ui-navigation-i18n.md) | The whole needs-review status now opens/focuses the operation panel without opening logs; the unwanted retry sentence is removed; finalization sets `日报生成中` before its request; review UI/API errors are localized in Chinese, English and Thai. Live read-only browser QA and 393 tests pass; no price/finalize/cancel/upload mutation was invoked. | | 2026-08-06 | Integer review prices and finalization repair | Deployed repair; run-67 checkpoint is historical | [Evidence topic](topics/2026-08-06-integer-review-finalize-repair.md) | Review amounts now display/accept integers and normalize to canonical `.00`. Migration 018 fixes the omitted manual-manifest artifact kind and the Finance INSERT now has 20 binds; rollback probes, live readiness and the then-current 387-test suite passed. Run 67 was still frozen at `200/0` when that repair ended; later browser-visible lifecycle state is recorded in the UI follow-up topic. | | 2026-08-06 | Live Daily price-review rollout and `0805.XML` acceptance | Historical initial-review snapshot; superseded by repair topic | [Evidence topic](topics/2026-08-06-live-daily-price-review-rollout.md) | Reconciled live 016 semantics without rerunning it, corrected and applied 017 after an exact up/down rollback probe, closed runs 61–63, fixed a delivery-placeholder runtime bug, and proved run 65 reached the two-key live panel with no Finance/report/outbox effect. Later runs and current retry instructions are in the integer/finalization repair topic. | @@ -51,7 +54,7 @@ Use this index for searchable, traceable evidence records. | 2026-07-30 | Daily page visual polish | Active | [Evidence topic](topics/2026-07-30-daily-page-visual-polish.md) | The daily surface now preserves workspace-title hierarchy, exposes task log as a real button and centers a simplified upload station with a compact action. Thirty-three focused tests and four responsive live widths passed without overflow or browser issues. | | 2026-07-30 | Web login runtime mismatch | Resolved; authenticated runtime active | [Evidence topic](topics/2026-07-30-web-login-runtime-mismatch.md) | Keychain-backed login is active under detached Screen on `*:8766`. A 2026-07-31 Keychain-only rotation and controlled restart reject the prior pair, accept the new pair and pass session/readiness/logout plus loopback/LAN checks without business writes. DHCP has the current LAN entry at `.48`; the password is still not distinct from the username, and reboot-persistent supervision remains open. | | 2026-07-30 | Daily upload filename provenance | Implemented/migrated; post-restart write acceptance pending | [Evidence topic](topics/2026-07-30-daily-upload-filename-provenance.md) | Migration 013 separates `processing_runs.uploaded_filename` from canonical artifact `source.xml`; history/trace use only the upload provenance and old rows render unknown. The authenticated runtime is active; one controlled no-PII upload remains to verify the live write path. | -| 2026-07-30 | Task-log relocation and daily-only scope | Historical before-state; superseded by the 2026-07-31 upload interaction update | [Evidence topic](topics/2026-07-30-task-log-relocation-and-scope.md) | The desktop header opens the black task console as a native modal, and history selection still reveals the matching job. The earlier upload-triggered opening is superseded; query inspection still proves both history and trace roots are limited to `pipeline_type = 'opera_daily'`. | +| 2026-07-30 | Task-log relocation and daily-only scope | Historical before-state; interaction superseded through 2026-08-11 | [Evidence topic](topics/2026-07-30-task-log-relocation-and-scope.md) | The modal relocation and daily-only query scope remain factual. Upload-triggered opening was removed on 2026-07-31, and Daily-row selection was removed on 2026-08-11; the header `任务日志` link is now the sole dialog opener. | | 2026-07-30 | Company-report path and July month-end readiness | Historical pre-change readiness snapshot; current generation rule superseded | [Evidence topic](topics/2026-07-30-company-report-path-and-month-end-readiness.md) | Missing/unmatched Group Codes leave Booking Room blank and current July projection previews valid 5/5. The topic records the prior July completion boundary and the remaining acceptance work; current-month incomplete periods are now also generatable, with rerun after completion when needed. | | 2026-07-30 | Channel BI post-update data contamination | Active; repair not yet authorized | [Evidence topic](topics/2026-07-30-channel-bi-post-update-data-contamination.md) | Four hash-matched real files total 416 rooms; live API/monthly V04 show 417 because active Finance version 2 is a one-room local fixture. An open BI view can also stay at pre-07-23 value 308 because analytics does not refresh after upload. | | 2026-07-30 | Monthly publication live acceptance | Implemented; current data caveat active | [Evidence topic](topics/2026-07-30-monthly-publication-live-acceptance.md) | Publication mechanics remain verified. Later runs reached V04 active with 417 rows and five published events; 416 are real and one is the known fixture. Browser/access logs previously proved automatic list discovery without refresh. | diff --git a/.project-docs/50-evidence/topics/2026-07-30-task-log-relocation-and-scope.md b/.project-docs/50-evidence/topics/2026-07-30-task-log-relocation-and-scope.md index 1bc9a57..380781f 100644 --- a/.project-docs/50-evidence/topics/2026-07-30-task-log-relocation-and-scope.md +++ b/.project-docs/50-evidence/topics/2026-07-30-task-log-relocation-and-scope.md @@ -3,7 +3,7 @@ ## Metadata - Date: 2026-07-30 -- Status: Historical before-state; superseded by the 2026-07-31 upload interaction update +- Status: Historical before-state; opening interactions superseded through 2026-08-11 - Scope: Desktop task-log information architecture, interaction and backend query boundary - Confidence: Fact - Source: `arr_web/static/`, `arr_web/repository.py`, focused tests and live local browser verification @@ -15,7 +15,8 @@ - The desktop header position formerly occupied by `手机看板` now contains a `任务日志` button. - The sole existing black task console moved out of the 日报处理 panel into a native modal dialog; no duplicate console or alternate trace implementation was introduced. - Before the 2026-07-31 upload interaction update, starting an upload opened the dialog. Mouse or keyboard activation - of a daily-history row still opens the selected job's trace. + of a daily-history row also opened the selected job's trace until the 2026-08-11 entry-boundary correction. Current + behavior allows only the header `任务日志` link to open the dialog. - Closing the dialog returns focus to the opener. Trace polling runs only while the dialog is open and the selected job remains active. - The `/h5` route and assets remain intact, but the desktop header no longer links to them. diff --git a/.project-docs/50-evidence/topics/2026-08-06-daily-review-ui-navigation-i18n.md b/.project-docs/50-evidence/topics/2026-08-06-daily-review-ui-navigation-i18n.md index 564b508..a806a9d 100644 --- a/.project-docs/50-evidence/topics/2026-08-06-daily-review-ui-navigation-i18n.md +++ b/.project-docs/50-evidence/topics/2026-08-06-daily-review-ui-navigation-i18n.md @@ -4,6 +4,10 @@ Date: 2026-08-06 Status: implemented and runtime-active Scope: authenticated desktop Daily Report UI only +Interaction note (2026-08-11): the row-owned task-log behavior described as the surrounding before-state below is +superseded in local source by the Daily row / task-log entry-boundary correction. After that change is deployed, only +the top-right `任务日志` link opens the log dialog. + ## Reported Problems - Clicking `待人工处理` opened the task log instead of the manual-price operation panel. diff --git a/.project-docs/50-evidence/topics/2026-08-11-0806-workbook-to-res-detail-xml.md b/.project-docs/50-evidence/topics/2026-08-11-0806-workbook-to-res-detail-xml.md new file mode 100644 index 0000000..98199c0 --- /dev/null +++ b/.project-docs/50-evidence/topics/2026-08-11-0806-workbook-to-res-detail-xml.md @@ -0,0 +1,32 @@ +# `8.6 修正.XML` workbook-to-XML conversion + +## Metadata + +- Date: 2026-08-11 +- Status: Converted and independently accepted by the active processor +- Scope: user-supplied `8.6 修正.XML` workbook and generated upload artifact +- Confidence: High for the generated artifact; no original workbook bytes were changed + +## Input classification + +- The supplied file is an XLSX ZIP package despite its `.XML` filename (`application/vnd.openxmlformats-officedocument.spreadsheetml.sheet`). +- It contains three sheets, with 181 data rows on `Sheet1`; every row has the same `20260806` / `06-08-26` business-date pair. +- The first sheet exposes the flattened Opera `RES_DETAIL` fields needed by the ARR parser, plus separate flattened comment and trace columns. + +## Conversion and validation + +- Generated a new UTF-8 `RES_DETAIL` XML with the fixed path + `LIST_G_GROUP_BY1/G_GROUP_BY1/LIST_G_RESERVATION/G_RESERVATION`. +- Mapped the workbook's required reservation fields without changing business values; flattened comment and trace values + were placed in the parser's existing nested `RES_COMMENT` and `TRACE_TEXT` paths. The workbook remained read-only. +- Output artifact: `8.6 修正-可上传.XML`, SHA-256 + `27b41f4338967ba2b5ae03161fa7eb7b63929a9708c0106e01a740e6c2cc1c91`. +- `xmllint`, strict UTF-8 decoding and ARR processor 4.0.0 all pass. Processor result is `success` for business date + `2026-08-06`: 181 source rows, 37 rate-code exclusions, 29 duplicates, 115 retained rows, zero price-unmatched + rows and zero review items. No daily/Finance/database write was performed. + +## Boundary + +This artifact is a deterministic reconstruction from the supplied workbook, not a byte-for-byte restoration of the +malformed original Opera export. Upload the generated XML as a new task; keep the source workbook and the original +malformed XML as historical inputs. diff --git a/.project-docs/50-evidence/topics/2026-08-11-0806-xml-invalid-utf8.md b/.project-docs/50-evidence/topics/2026-08-11-0806-xml-invalid-utf8.md new file mode 100644 index 0000000..8d70dcc --- /dev/null +++ b/.project-docs/50-evidence/topics/2026-08-11-0806-xml-invalid-utf8.md @@ -0,0 +1,41 @@ +# `0806.XML` invalid UTF-8 diagnosis + +## Metadata + +- Date: 2026-08-11 +- Status: Read-only input diagnosis; source re-export required +- Scope: exact desktop `0806.XML`, processor 4.0.0 isolated replay, public deployment health probe +- Confidence: High for the inspected file; the authenticated remote artifact hash was not read + +## Observed behavior + +- The public ARR endpoint at `8.138.234.141:8765` remained healthy: anonymous `/healthz` returned HTTP 200 while + authenticated detail endpoints remained protected. +- The inspected `0806.XML` is 694,286 bytes with SHA-256 + `b41c103bfad7f9f0abb25af159a0c5920f0f9b89e156d31919521d057b62639f`. +- An isolated active-processor replay exited 3 with `status=failed`, `business_date=null`, zero parsed source rows and + exactly one `XML_PARSE_ERROR`: `not well-formed (invalid token): line 1872, column 0`. +- The structured result is processor 4.0.0 / Schema 4.0 with `activation_eligible=false`; every outcome count and + review count is zero. No price matching was reached. + +## Root cause + +- The XML declaration says UTF-8, but line 1872 inside a `TRACE_TEXT` value begins with bytes + `ED A0 BC ED BE 89`. +- Those six bytes are the CESU-8 representation of UTF-16 surrogate pair `U+D83C U+DF89`, whose intended Unicode + scalar is `U+1F389`. UTF-8 must encode that scalar as one four-byte sequence; XML 1.0 also forbids surrogate code + points as PCDATA. +- The file contains exactly one such surrogate pair. Its closing XML structure is present, so this is an encoding + defect rather than a truncated export. +- Manual price review is intentionally unavailable: only a non-empty error set consisting entirely of + `PRICE_UNMATCHED` can enter `review_required`; `XML_PARSE_ERROR` is a formal input failure before pricing. + +## Safe recovery boundary + +Re-export the 2026-08-06 Opera XML as standards-compliant UTF-8, preferably after the upstream trace text no longer +contains the mis-encoded character, then upload the new export as a fresh task. Do not edit the registered source +artifact or convert the failed task into a review case. A fresh replay is still required to discover any later price +or business-rule issues after parsing succeeds. + +No original XML byte, upload, review item, database fact, Finance version, object-store object, event or runtime process +was changed during this diagnosis. diff --git a/.project-docs/50-evidence/topics/2026-08-11-daily-row-task-log-entry-boundary.md b/.project-docs/50-evidence/topics/2026-08-11-daily-row-task-log-entry-boundary.md new file mode 100644 index 0000000..f8cdeaf --- /dev/null +++ b/.project-docs/50-evidence/topics/2026-08-11-daily-row-task-log-entry-boundary.md @@ -0,0 +1,47 @@ +# Daily Row / Task-Log Entry Boundary + +Date: 2026-08-11 +Status: implemented locally; production deployment pending +Scope: authenticated desktop Daily Report interaction only + +## Reported Problem + +On the production Daily page, clicking a concrete history row opened the technical task-log dialog. The intended +information architecture is stricter: logs are an on-demand utility available only from the top-right `任务日志` link; +ordinary users should not reach them while inspecting business rows. + +## Root Cause + +The row behavior was explicit rather than accidental bubbling. `renderJobs()` emitted every row with `role=button`, +`tabindex=0`, a selected state and a log-specific accessible label. Delegated click and Enter/Space handlers then called +`selectJob(..., { showLog: true })`; CSS reinforced the action with a pointer cursor, focus ring and selected-row tint. + +## Change + +- Render Daily history as ordinary table rows with no row action attributes. +- Remove the row click and Enter/Space log activation paths and the `showLog` option from internal trace selection. +- Remove the row pointer/focus/selected styling so appearance and accessibility semantics match the inert behavior. +- Preserve the explicit `待人工处理` button, report download link and top-right `任务日志` dialog trigger. + +No API, trace query, processor, Finance, database, upload or monthly-publication behavior changed. + +## Verification + +- `node --check arr_web/static/app.js`: passed. +- All `test_arr_web*.py` tests: 81 passed in 1.105 seconds. +- The focused static contract proves `openTaskLog();` has exactly one caller, tied to `#task-log-trigger`, and rejects + any row key handler, log-selection call, interactive semantics or `.job-row` styling. +- An isolated local page served the real edited HTML/CSS/JavaScript with two mock Daily records. Browser checks proved: + - clicking ordinary `0805.XML` kept `#task-log-dialog.open === false` and left the hash at `#daily`; + - clicking `待人工处理` focused the visible review panel with one issue and kept the log closed; + - clicking the top-right `任务日志` link opened the dialog; + - the ordinary row had no role/tabindex, computed cursor was `auto`, and the console had zero errors. + +The local browser used only mock GET responses. It did not upload, save a price, finalize/cancel a review, download an +artifact, write Finance/database state or contact a production mutation endpoint. + +## Deployment Boundary + +An unauthenticated production static-asset read correctly redirected to login, and no credentials or deployment path +were inferred. The edited source has not been committed, pushed, restarted or deployed to `8.138.234.141:8765`; the +production symptom therefore remains until the controlled release step is authorized and completed. diff --git a/.project-docs/60-reflection/cases/2026-08-11-technical-surfaces-need-explicit-entry-controls.md b/.project-docs/60-reflection/cases/2026-08-11-technical-surfaces-need-explicit-entry-controls.md new file mode 100644 index 0000000..64fe621 --- /dev/null +++ b/.project-docs/60-reflection/cases/2026-08-11-technical-surfaces-need-explicit-entry-controls.md @@ -0,0 +1,39 @@ +# Reflection: Technical Surfaces Need Explicit Entry Controls + +## Trigger + +The user corrected the production Daily interaction: clicking a concrete business row unexpectedly opened the full +technical task log, even though a named `任务日志` utility already existed in the header. + +## Expected Behavior + +Inspecting business data should be inert unless a cell exposes a clear business action. Dense diagnostic material +should appear only after an explicit, accurately named user action. + +## Actual Behavior + +Every Daily row was visually and semantically a hidden log button. Mouse click, Enter and Space opened the dialog, so +the same technical surface had both a deliberate header entry and an implicit whole-row entry. + +## Root Cause + +- A convenient job-selection interaction was conflated with permission to reveal diagnostic detail. +- Handler removal alone had not been treated as enough; row role, focusability, cursor and selected styling also encoded + the obsolete action. +- Earlier acceptance covered the special needs-review button but did not assert that all ordinary rows were inert. + +## Lesson + +When a product has a dedicated diagnostic control, make it the sole opener unless row-level diagnostics are an explicit +requirement. A behavior change is complete only when event handlers, keyboard paths, accessibility semantics, visual +affordances and regression tests all agree. + +## Action + +- Keep Daily rows informational and retain only explicit download/review controls inside them. +- Assert that the named header utility is the only task-log opener. +- Verify ordinary row, business-action and diagnostic-entry clicks separately in browser acceptance. + +## Promotion + +Promoted to project success criteria, current state, focused static UI contracts and the 2026-08-11 interaction evidence. diff --git a/.project-docs/60-reflection/reflection-index.md b/.project-docs/60-reflection/reflection-index.md index 15b146c..d8663f2 100644 --- a/.project-docs/60-reflection/reflection-index.md +++ b/.project-docs/60-reflection/reflection-index.md @@ -4,6 +4,7 @@ Use this index for second-order workflow lessons. | Date | Reflection | Trigger | Action | Detail | |---|---|---|---|---| +| 2026-08-11 | Technical surfaces need explicit entry controls | The user corrected the Daily table because inspecting a business row unexpectedly opened a dense technical log | Keep ordinary rows informational, place technical utilities behind named controls, and remove interaction styling/semantics with the handler | [Case](cases/2026-08-11-technical-surfaces-need-explicit-entry-controls.md) | | 2026-07-31 | Formatting is not business state | A yellow final Booking row was initially at risk of being interpreted as cancellation | Make cancellation text-based, preserve formatting as non-authoritative and keep a real-style regression | [Case](cases/2026-07-31-formatting-is-not-business-state.md) | | 2026-07-30 | Coordinate hot static assets with the long-lived backend | Login static files activated before backend restart; later launchd ownership also hit macOS Desktop privacy | Feature-detect mixed versions, gate restart on all secrets, use a secret-free launcher and a desktop-authorized supervisor until deployment leaves Desktop | [Case](cases/2026-07-30-coordinate-hot-static-and-long-lived-backend.md) | | 2026-07-30 | Distinguish a database snapshot from batch completeness | A 15:44 three-file snapshot was incorrectly declared the clean total before the 16:12 07-23 commit | Timestamp snapshots, enumerate expected dates and reconcile recent/in-flight runs before declaring completeness | [Case](cases/2026-07-30-distinguish-snapshot-from-batch-completeness.md) | diff --git a/.project-docs/90-maintenance/stale-items.md b/.project-docs/90-maintenance/stale-items.md index c289800..98e5d36 100644 --- a/.project-docs/90-maintenance/stale-items.md +++ b/.project-docs/90-maintenance/stale-items.md @@ -4,8 +4,9 @@ | Date | Document / Module | Issue | Next Action | |---|---|---|---| -| 2026-08-06 | Daily `PRICE_UNMATCHED` final Finance acceptance / business date 2026-08-05 | Integer UI/API and both finalization defects are repaired and deployed: migration 018 admits the immutable manifest artifact, the Finance INSERT bind count is exact, and a real PostgreSQL rollback slice passes. Latest run 67 is still revision-2 `generation_failed` with frozen GRP1/LBLT `200/0`; it has no Finance version or run outbox. Run 66 separately retains `2300/0`, run 65 remains unset, and rejected version 28 remains immutable | Refresh/login, open latest run 67, verify `200/0` are the intended approved values, then explicitly retry generation once. Do not retry run 66 by mistake. After success, prove 153 retained rows with 2 `manual_review` rows, one active Finance version, matching frozen-manifest SHA and exactly one new final daily-commit/monthly event; do not modify version 28 | +| 2026-08-11 | Daily row / task-log production deployment | Local source and tests now make the header `任务日志` link the sole dialog opener, but the user-reported production site `8.138.234.141:8765` still serves the old row-opening behavior. This task did not push, restart or deploy the production service | Release the scoped frontend/tests through the controlled production workflow, then perform authenticated read-only QA: ordinary row leaves the dialog closed, review control focuses the operation panel, header control opens the dialog, and download remains independent | | 2026-08-11 | `booking_ingestion.excel` header compatibility deployment | Parser 2.1.0 now supports the approved legacy Thai and bilingual Lian Tai/QBD full headers through exact normalized aliases; synthetic/coordinator tests, four exact workbook replays and wrong-report rejections pass. The UI remains one-file and activation replaces the singleton full source rather than appending separate files | Deploy/restart Web, verify authenticated readiness, then run the four supplied files through a read-only parser replay before any separately authorized upload/review/activation. Treat multi-file append as a separate product decision; QBD's pending room-type items still require the existing manual review workflow | +| 2026-08-06 | Daily `PRICE_UNMATCHED` final Finance acceptance / business date 2026-08-05 | Integer UI/API and both finalization defects are repaired and deployed: migration 018 admits the immutable manifest artifact, the Finance INSERT bind count is exact, and a real PostgreSQL rollback slice passes. Latest run 67 is still revision-2 `generation_failed` with frozen GRP1/LBLT `200/0`; it has no Finance version or run outbox. Run 66 separately retains `2300/0`, run 65 remains unset, and rejected version 28 remains immutable | Refresh/login, open latest run 67, verify `200/0` are the intended approved values, then explicitly retry generation once. Do not retry run 66 by mistake. After success, prove 153 retained rows with 2 `manual_review` rows, one active Finance version, matching frozen-manifest SHA and exactly one new final daily-commit/monthly event; do not modify version 28 | | 2026-07-31 | Booking raw Excel parser/review business acceptance | Parser 2.1, migrations 014/015, atomic draft repository, routes, editor, HTTP transport and current runtime are ready. Supplied-workbook replay, expanded tests, live 5/5 processing and a real-PostgreSQL create/edit/activate/rollback slice pass. No authorized real workbook has replaced the current source | Run one operator-authorized complete-workbook upload/review/activation, then one released five-company job and verify all downloads | | 2026-07-31 | Booking review audit depth | Migration 015 stores current confirmed/pending/deleted state but no reviewer, reason, explicit review event, old/new values or immutable revision history. Repository logic serializes and gates activation, but declarative DB guards are limited and same-artifact re-extraction deletes earlier unactivated edits | Confirm whether single-operator latest-state editing is sufficient. If review must be auditable, add actor/reason/event or revision tables, optimistic concurrency and DB transition guards before migration acceptance | | 2026-07-31 | Booking Excel current-source runtime acceptance | Current port 8766 loads the review composition with all readiness flags true; migrations 014/015 are recorded and `booking.current_source_batch` still selects Markdown batch 1. A 14:14 +08 read-only recheck found no reviewing draft; the prior draft is no longer an active task-creation blocker and did not switch the current source. The Markdown five-workbook baseline has been frozen with exact hashes | Run one Markdown-backed five-company Web job after the July period completes if a final-period snapshot is desired, then verify downloads. Keep any later complete-workbook activation operator-authorized, then compare its five overlapping Group Codes with the frozen baseline | diff --git a/arr_web/static/app.js b/arr_web/static/app.js index 07885e0..c48c224 100644 --- a/arr_web/static/app.js +++ b/arr_web/static/app.js @@ -410,7 +410,7 @@ : chip(job.status, jobStatus); const canDownload = state.health.download_ready && job.status === "succeeded" && job.daily_report_sha256; return ` - + ${escapeHtml(job.filename || "—")} ${statusMarkup}${job.failure_code ? `${escapeHtml(job.failure_code)}` : ""} ${escapeHtml(job.arrival_date ? formatDate(job.arrival_date) : "—")} @@ -1009,14 +1009,13 @@ } } - async function selectJob(jobId, { showLog = false } = {}) { + async function selectJob(jobId) { if (!jobId) return; clearTracePoll(); const changed = state.selectedJobId !== jobId; state.selectedJobId = jobId; if (changed) state.jobTrace = null; renderJobs(state.jobs); - if (showLog) openTaskLog(); await loadJobTrace(true, !changed); } @@ -2611,19 +2610,7 @@ if (reviewTrigger) { event.preventDefault(); openDailyPriceReview(reviewTrigger.dataset.openPriceReview, true); - return; } - if (event.target.closest("a, button")) return; - const row = event.target.closest("[data-job-id]"); - if (row) selectJob(row.dataset.jobId, { showLog: true }); - }); - $("#jobs-body").addEventListener("keydown", (event) => { - if (!["Enter", " "].includes(event.key)) return; - if (event.target.closest("a, button")) return; - const row = event.target.closest("[data-job-id]"); - if (!row) return; - event.preventDefault(); - selectJob(row.dataset.jobId, { showLog: true }); }); $("#daily-price-review-body").addEventListener("click", (event) => { const save = event.target.closest("[data-daily-price-review-save]"); diff --git a/arr_web/static/styles.css b/arr_web/static/styles.css index be7a7ce..f69be07 100644 --- a/arr_web/static/styles.css +++ b/arr_web/static/styles.css @@ -295,10 +295,6 @@ th { color: var(--muted); background: #fbfcfe; font-size: 10px; font-weight: 750 td { font-size: 13px; } tbody tr:last-child td { border-bottom: 0; } tbody tr:hover td { background: #fafcff; } -.job-row { cursor: pointer; } -.job-row.is-selected td { background: #f1f6ff; } -.job-row.is-selected td:first-child { box-shadow: inset 3px 0 var(--blue); } -.job-row:focus-visible { outline: 3px solid rgba(37, 99, 235, .28); outline-offset: -3px; } .job-failure-code { max-width: 180px; margin-top: 4px; overflow: hidden; display: block; color: #b42318; font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; font-size: 8px; font-weight: 700; text-overflow: ellipsis; white-space: nowrap; } .filename { max-width: 260px; overflow: hidden; display: block; font-weight: 620; text-overflow: ellipsis; } .cell-subtle { color: var(--muted); } diff --git a/tests/test_arr_web_daily_price_review_ui.py b/tests/test_arr_web_daily_price_review_ui.py index 0e01df1..49c9c20 100644 --- a/tests/test_arr_web_daily_price_review_ui.py +++ b/tests/test_arr_web_daily_price_review_ui.py @@ -32,7 +32,9 @@ class DailyPriceReviewUiContractTests(unittest.TestCase): handler_end = self.script.index('$("#daily-price-review-body")', handler_start) handlers = self.script[handler_start:handler_end] self.assertIn("openDailyPriceReview(reviewTrigger.dataset.openPriceReview, true)", handlers) - self.assertGreaterEqual(handlers.count('event.target.closest("a, button")'), 2) + self.assertNotIn('$("#jobs-body").addEventListener("keydown"', handlers) + self.assertNotIn('event.target.closest("a, button")', handlers) + self.assertNotIn('closest("[data-job-id]")', handlers) self.assertIn('id="daily-price-review-panel" aria-labelledby="daily-price-review-title" tabindex="-1"', self.html) self.assertIn("panel.focus({ preventScroll: true });", self.script) diff --git a/tests/test_arr_web_task_log_ui.py b/tests/test_arr_web_task_log_ui.py index b209279..8bea7b4 100644 --- a/tests/test_arr_web_task_log_ui.py +++ b/tests/test_arr_web_task_log_ui.py @@ -46,13 +46,30 @@ class TaskLogStaticContractTests(unittest.TestCase): def test_dialog_open_close_and_polling_contract(self) -> None: self.assertIn('$("#task-log-trigger").addEventListener("click", (event) => {', self.script) self.assertIn("event.preventDefault();", self.script) - self.assertIn("openTaskLog();", self.script) + self.assertEqual(self.script.count("openTaskLog();"), 1) self.assertIn('$("#close-task-log").addEventListener("click", closeTaskLog)', self.script) self.assertIn('$("#task-log-dialog").addEventListener("close", clearTracePoll)', self.script) self.assertIn("|| !taskLogIsOpen()", self.script) - self.assertIn("{ showLog: true }", self.script) + self.assertNotIn("showLog", self.script) self.assertNotIn('{ scroll: true }', self.script) + def test_daily_rows_are_not_task_log_controls(self) -> None: + render_start = self.script.index("function renderJobs(jobs)") + render_end = self.script.index("function reviewPrice(value)", render_start) + renderer = self.script[render_start:render_end] + self.assertNotIn('class="job-row', renderer) + self.assertNotIn('data-job-id=', renderer) + self.assertNotIn('tabindex="0" role="button"', renderer) + self.assertNotIn('aria-selected=', renderer) + + handler_start = self.script.index('$("#jobs-body").addEventListener("click"') + handler_end = self.script.index('$("#daily-price-review-body")', handler_start) + handlers = self.script[handler_start:handler_end] + self.assertNotIn('$("#jobs-body").addEventListener("keydown"', handlers) + self.assertNotIn('closest("[data-job-id]")', handlers) + self.assertNotIn("selectJob(", handlers) + self.assertNotIn(".job-row", self.styles) + def test_upload_keeps_progress_inline_without_opening_log(self) -> None: self.assertIn('id="upload-progress"', self.html) self.assertIn('id="upload-progress-track"', self.html)