feat(page-history): время работы над статьёй — число + суточный punch-card (#395) #551
Open
agent_coder
wants to merge 3 commits from
feat/395-work-time into develop
pull from: feat/395-work-time
merge into: vvzvlad:develop
vvzvlad:main
vvzvlad:feat/529-search-lexical
vvzvlad:feat/506-apikeys-ui
vvzvlad:feat/502-mcp-md-modes
vvzvlad:feat/492-incremental-render
vvzvlad:fix/549-hardbreak-md-canon
vvzvlad:feat/542-resolve-undo
vvzvlad:fix/505-drawio-strip-comments
vvzvlad:fix/503-hardbreak-canon
vvzvlad:docs/471-breadcrumb-permission
vvzvlad:fix/534-mcp-spaceid
vvzvlad:feat/481-version-coherence
vvzvlad:fix/535-list-seam-coalesce
vvzvlad:develop
vvzvlad:fix/520-recovery-escalation
vvzvlad:fix/525-insert-unloaded
vvzvlad:fix/522-internal-links
vvzvlad:fix/507-drawio-cyrillic
vvzvlad:fix/541-reconnect-timeout
vvzvlad:feat/501-mcp-apikey-auth
vvzvlad:feat/493-converter
vvzvlad:feat/370-page-versioning
vvzvlad:fix/515-code-emphasis
vvzvlad:fix/523-tree-dnd
vvzvlad:fix/517-toasts
vvzvlad:feat/495-tails
vvzvlad:feat/494-mcp-registry-guards
vvzvlad:feat/496-suggestions-audit
vvzvlad:integ/497-wave1
vvzvlad:feat/488-client-fsm
vvzvlad:ci/476-publish-gates
vvzvlad:feat/489-tool-resilience
vvzvlad:feat/487-terminal-lifecycle
vvzvlad:fix/486-iter1-prod-fires
vvzvlad:refactor/347-client-md-paste
vvzvlad:perf/479-getpage-cache
vvzvlad:refactor/449-write-invariants
vvzvlad:refactor/450-split-client
vvzvlad:feat/443-get-page-context
vvzvlad:feat/443-get-tree
vvzvlad:feat/443-search
vvzvlad:docs/415-lossy-descriptions
vvzvlad:feat/413-markdown-default
vvzvlad:refactor/412-camelcase-tool-names
vvzvlad:feat/425-drawio-graph
vvzvlad:fix/464-diffdocs-cpu-guard
vvzvlad:refactor/411-update-page-markdown
vvzvlad:feat/424-drawio-rules
vvzvlad:fix/409-invalid-node-validation
vvzvlad:refactor/448-generate-inventory
vvzvlad:refactor/445-execute-mapping
vvzvlad:refactor/446-derive-client-types
vvzvlad:fix/447-registry-stamp-ci
vvzvlad:fix/444-agent-loop-guards
vvzvlad:fix/396-sendnow-detached-run
vvzvlad:fix/452-immutable-cache-control
vvzvlad:docs/footnote-authoring-comment-cleanup
vvzvlad:feat/437-error-diagnostics
vvzvlad:fix/442-cursor-pagination
vvzvlad:feat/419-footnote-normalize
vvzvlad:perf/399-comment-resolve-async
vvzvlad:perf/435-collab-token-cache
vvzvlad:perf/344-background-rerenders
vvzvlad:perf/348-backend-lowhanging
vvzvlad:feat/423-drawio-crud
vvzvlad:perf/401-collab-hotpath
vvzvlad:feat/430-live-reconnect
vvzvlad:perf/400-collab-session
vvzvlad:refactor/414-dedup-node-ops
vvzvlad:feat/417-new-comments-signal
vvzvlad:feat/408-createcomment-hints
vvzvlad:feat/407-persist-tool-errors
vvzvlad:feat/420-footnote-render
vvzvlad:dummy-review/mcp-hang-fix
vvzvlad:dummy-review/mcp-hang-base
vvzvlad:feat/381-resumable-sse-pr1
vvzvlad:feat/git-sync-2
vvzvlad:docs/agents-workspace-build-order
vvzvlad:feature/offline-sync
vvzvlad:perf/342-code-splitting
vvzvlad:perf/346-compression-cache
vvzvlad:feat/196-multi-cursor
vvzvlad:perf/343-typing-latency
vvzvlad:docs/how-to-test
vvzvlad:fix/ai-sdk-partial-output-oom
vvzvlad:test/351-generative-converter
vvzvlad:feat/371-roles-catalog
vvzvlad:refactor/345-server-converter
vvzvlad:refactor/294-spec-registry-cont
vvzvlad:fix/363-migration-order
vvzvlad:fix/e2e-callout-and-gate-build
vvzvlad:fix/docker-re2-toolchain
vvzvlad:feat/git-sync
vvzvlad:fix/media-roundtrip-stability
vvzvlad:fix/340-comment-panel-perf
vvzvlad:fix/332-deferred-tools
vvzvlad:fix/329-ephemeral-suggestions
vvzvlad:fix/330-search-in-page
vvzvlad:fix/328-resolved-anchor-spam
vvzvlad:fix/331-intraline-diff
vvzvlad:fix/324-coverage-gate
vvzvlad:fix/325-mobile-390
vvzvlad:feat/293-A-git-sync-package
vvzvlad:feat/300-avatar-oklch
vvzvlad:fix/321-banner-mobile
vvzvlad:feat/300-avatar-colors
vvzvlad:feat/315-comment-suggestions
vvzvlad:feat/scroll-restore-stable-wait
vvzvlad:feat/300-agent-avatar-stack
vvzvlad:feat/300-avatar-polish
vvzvlad:refactor/294-tool-spec-registry
vvzvlad:feat/scroll-restore-ux
vvzvlad:fix/responsive-tablet-sidebar
vvzvlad:feature/ai-chat-page-change-observability
vvzvlad:image-inline-center
vvzvlad:fix/283-short-remap-title
vvzvlad:fix/283-slash-layout
vvzvlad:image-inline-row
vvzvlad:feat/276-ai-chat-dock
vvzvlad:fix/269-table-menu-refocus
vvzvlad:docs/dev-stand-guide
vvzvlad:feat/266-scroll-position
vvzvlad:fix/260-collab-docname-slugid
vvzvlad:test/244-phase2-tail
vvzvlad:fix/262-reindex-progress-realtime
vvzvlad:fix/258-changelog-compare-links
vvzvlad:fix/244-dataloss-bugs
vvzvlad:feat/246-spoiler
vvzvlad:feat/221-image-captions
vvzvlad:test/244-part-b
vvzvlad:feat/251-intentional-clear
vvzvlad:fix/embeddings-reindex-progress
vvzvlad:refactor/193-tool-spec-registry
vvzvlad:fix/255-ws-redis-adapter-leak
vvzvlad:fix/252-e2e-open-handles
vvzvlad:feat/229-catalog-yaml
vvzvlad:feat/243-blob-sandbox
vvzvlad:feat/228-inline-footnotes
vvzvlad:fix/qa-ui-bugs-216-218
vvzvlad:feature/agent-roles-catalog
vvzvlad:fix/share-alias-rename
vvzvlad:fix/ai-chat-empty-render
vvzvlad:feat/191-chat-doc-binding
vvzvlad:feat/201-temporary-notes
vvzvlad:feat/198-interrupt-agent
vvzvlad:feat/ai-chat-full-history
vvzvlad:feat/199-ai-generate-title
vvzvlad:feat/205-share-aliases
vvzvlad:batch/issues-189-187-170
vvzvlad:feat/170-mcp-test-button
vvzvlad:feat/189-context-badge
vvzvlad:feat/198-interrupt-agent-send-now
vvzvlad:fix/issues-190-159
vvzvlad:fix/ai-chat-new-chat-during-stream
vvzvlad:fix/ai-chat-stream-perf
vvzvlad:batch/issues-2026-06-25
vvzvlad:feat/ai-chat-persistent-history
vvzvlad:fix/ai-chat-copy-chat-wysiwyg
vvzvlad:fix/ai-stream-reset-resilience
vvzvlad:fix/ai-stream-undici-timeout
vvzvlad:fix/footnote-review-1227-followup
vvzvlad:fix/ai-chat-token-counter-realtime
vvzvlad:docs/manual-qa-test-plan
3 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
79b2da686b |
fix(work-time): clip cross-class padding; gate & payload fixes (#395)
F1: pad-clip adjacent different-class sessions at the raw-gap midpoint so workMs and agentOnlyMs can never double-count the same wall-clock (default agentTGap 7m < pIn+pOut 10m made a work-session-ending-in-agent overlap a nearby agent_only run). Reword the config guard/comment: cross-class disjointness is now structural, tGap≥pIn+pOut is a kept sanity bound. Add a cross-class no-double-count test and a real seeded property/fuzz test (250 random timelines × 4 tz) asserting per-class union, cross-class disjointness, and Σ per-day activeMs == workMs. F2: page.controller.spec — add a ForbiddenException view-gate reject test that asserts the rejection propagates and computeWorkTime is NOT reached, locking validateCanView before compute. F3: WorkTimeStat renders for agent-only pages (workMs==0, agentOnlyMs>0) with an `agent:` headline so the punch-card stays reachable. F4: drop the dead un-bucketed `sessions` list from the PageWorkTime response and the client work-time types (no client reads it; bucketByDay still consumes the core sessions internally). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
3e81f64415 |
fix(work-time): user idle breaks agent burst; cap tz length (#395)
Only an agent-sourced idle pulse extends an agent burst; a user idle (human supervision) now falls through to the human branch so the session is classified `work`, not `agent_only`. Add @MaxLength(64) to tz to match its comment. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
c674db2b2f |
feat(page-history): «время работы над статьёй» — число в UI + суточный punch-card (#395)
Оценка времени работы над страницей как сессионизация истории page_history по паузам бездействия (WakaTime-подобно), а не span между крайними правками, плюс drill-down в суточный таймлайн 24ч×дни. Зависит от #374 (kind + idle-пульс). Server: - Чистая computeWorkTime(rows, config) (§5): нормализация+дедуп → коллапс агентских всплесков по aiChatId → ОДИН проход сессионизации (порог зависит от пары: оба agent → agentTGap, иначе tGap; последняя сессия обязательно закрывается) → класс готовой сессии (все agent → agent_only, иначе work) → добивка (много-сэмпл → [first−P_in, last+P_out], одиночный скаляр → [t−P_single, t]) → метрики = union wall-clock по классу. Детерминированная, без БД. - Чистая bucketByDay(sessions, tz) (§6.3): union work/agent_only РАЗДЕЛЬНО, разрез по полуночи tz через нативный Intl (DST-корректно, без «+24ч»). Инвариант Σ activeMs == workMs держится по построению. - PageHistoryRepo.findTimelineByPageId — лёгкая проекция без content, ASC. - PageHistoryService.computeWorkTime + POST /pages/history/time, гейт validateCanView как у /history; атрибуция человек/агент по lastUpdatedSource. Client: - usePageWorkTime(pageId) шлёт tz зрителя (Intl локаль). - Кликабельное число «≈ 4 ч 30 мин» в шапке страницы (порог в тултипе). - Модалка-punch-card: 24-часовые дорожки по дням, окна work/agent разным цветом, одиночные (P_single) приглушены, сумма за день, пустой день «—», сворачивание длинных серий пустых дней, подпись tz + T_gap. i18n ru-RU + en-US. Tests: 22 юнита на computeWorkTime/bucketByDay (§7-фикстура ≈1ч32м vs ≈60h наив, закрытие последней сессии, коллапс/разрыв всплеска, idle-пульс, DST 23/25ч, полуночный разрез, инвариант, union-без-задвоения); 5 юнитов гейта контроллера; int-spec на реальном pg (проекция + совпадение с ядром); 6 клиентских на формат. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |