This file provides guidance to AI coding agents when working with code in this repository.
This file is a MAP, not the manual — rules and pointers only. The long unwindings live in
docs/ (loaded only when you touch that area) and at the code site. See
Maintaining this file before adding anything here.
| When you touch… | Read |
|---|---|
| taskbar geometry, boot/explorer-restart, D2D/DXGI, samplers, theme, process rows, 悬浮模式/贴边隐藏/全屏时隐藏, crash & stall forensics | docs/gotchas.md |
| anything spanning files: thread ownership, cross-thread contract, detail/pin model, startup & elevation, project layout | docs/architecture.md |
| a 设置 item's wiring to its sampler | docs/settings-plumbing.md |
| any of the nine scroll views | docs/scroll-stack.md |
你应具备软件工程化的能力来完成这个项目:所有工作不单是为了完成某个需求,而是为了持久地完成一个工程。如果你的代码不够工程化,后续也会消耗更多精力让你重构。当前代码中不够工程化的部分要主动提出,而不是参照之前的风格继续编写。
A Windows taskbar widget: a self-drawn taskbar overlay (default CPU/内存, 磁盘/GPU, 网络 ↑/↓;
外观 → 项目顺序 reorders; GPU 任务栏显示 = 使用率/温度/两者) that pops up a fluent acrylic detail
window on click. WPF on .NET
Framework 4.8. No main window; the only persistent UI is that overlay — optionally detached
into a free-floating card widget (设置 → 外观 → 悬浮模式, gotchas §36). The process stays alive via
ShutdownMode="OnExplicitShutdown"; exit is via the overlay's right-click menu.
dotnet build -c Debug # only toolchain on this machine — no VS MSBuild / nuget.exe
bin\Debug\net48\task_monitor.exeSDK-style csproj, net48, UseWPF=true. No tests.
A live exe locks the output, so dotnet build fails at the copy step with error MSB3021/MSB3027 naming task_monitor.exe (PID) — that is NOT a compile failure. Stop the
instance first, and always use the sentinel, never taskkill (why: docs/architecture.md):
touch bin/Debug/net48/shutdown.sentinel # 1s tick notices it and exits gracefully
dotnet build -c Debug
bin/Debug/net48/task_monitor.exe &The app always runs elevated and self-managed; relaunching from an unelevated shell pops a UAC
prompt on the secure desktop — that one step is the user's. Elevation gate and single-instance
mutex: docs/architecture.md.
UI testing is the USER's job — all of it, not just screenshots. That includes driving the real
windows (UI Automation, synthetic clicks/drags), screen capture with pixel/colour analysis, and
every judgement about how something looks, animates or feels. The agent's loop is: implement →
dotnet build → launch → verify from the log file and settings.yaml (plus code reading) →
hand the user a short checklist of what to click → wait for their feedback. Do NOT write or run
UI-test harnesses of your own; when something is only observable through the UI, say so and ask.
Git is READ-ONLY for the agent unless the user asks for a commit in that very turn. No git commit / --amend / push / tag / reset without an explicit request — do the work, leave it in
the working tree, and say it is uncommitted. "Finish the change" (including the docs-sync rule under
Maintaining this file) means editing the files, never committing them.
Editing files here: the shell is Windows PowerShell 5.1 and it is NOT text-safe.
Get-Content/Set-Content/Out-File/> default to the ANSI code page (GBK here) or UTF-16, so
routing a source file through them double-encodes it and eats characters — on 2026-09-22 that
destroyed 125 lines of TaskbarWindow.cs, and the repair cost a full reconstruction because the work
was uncommitted. Read and write text ONLY with the UTF-8-safe file tools (or .NET with an explicit
UTF-8 encoding — [IO.File]::ReadAllText($p, [Text.Encoding]::UTF8) is byte-exact, the cmdlet
defaults are not); keep the shell for processes and paths. Snapshot a file before any bulk rewrite.
Recovering: a diff against git HEAD reveals damage to HEAD's lines only, so "not in the archive"
never proves "we never wrote it".
GitHub 是主仓库,CNB 是镜像。 origin = GitHub(SSH),cnb = CNB(HTTPS——CNB 不支持 SSH,认证 = 固定用户名 cnb + 访问令牌)。日常只推 origin;.github/workflows/ 两个 workflow 负责同步与发布(细节看 workflow 本身):
sync-cnb.yml(main + v* tag)→git-sync把代码和 tag force 推到 CNB(CNB 是纯镜像,从无独有提交)。release.yml(v* tag)→ 校验 tag 与 csproj<Version>一致(不一致直接 fail)→dotnet build -c Release→gh release create→tools/publish-cnb-release.ps1建 CNB release 并上传task_monitor-<tag>-x64.exe。SecretCNB_TOKEN需 repo-code + repo-contents 两个读写 scope。
发布 = 先改 task_monitor.csproj 的 <Version>(版本号唯一来源——SDK 据它生成
AssemblyInformationalVersion,关于页与更新检测读它),并在 CHANGELOG.md 写好该 tag 的
## <tag> 段落(release 文案从这里提取,缺失/为空直接 fail),再 git tag vX.Y.Z && git push origin vX.Y.Z,之后全自动。CNB 云端集群全是 Linux 节点跑不了 net48 WPF,所以构建放在 GitHub 托管
Windows runner。publish-cnb-release.ps1 的三个坑(勿回退,见脚本注释):CNB API 必须显式
Accept: application/json;PUT/确认必须走 curl.exe(System.Uri 会解码 %2F 导致 500);保持
UTF-8 BOM(PowerShell 5.1 会按 GBK 误读)。
One process: WPF UI + one sampling STA + N overlay STAs. SamplingController owns the only
SystemSampler; TaskbarOverlayManager creates one native TaskbarWindow per selected display,
each with its own RenderState, DirectN pipeline and SetParent target. Layout still has 5 hit
slots and two taskbar families plus side-docked forms; floating mode remains a SINGLE window.
The WPF thread owns detail/settings/menu windows.
Single source of truth: controller publishes one snapshot; overlays redraw it independently, popups read the controller. Sampling and per-overlay heartbeats/diagnostics must all stay live.
Cross-thread contract (Taskbar→UI callbacks, UI→taskbar Set*/Request* methods) and the
pinned-mode invariants: docs/architecture.md; the 设置→sampler chains: docs/settings-plumbing.md.
All source is under src/, grouped by layer (App.* · Logger · AppSettings ·
VersionInfo · UpdateChecker · StartupTask at the top, then Sampling/ · Interop/ · UI/
with its Common/ Details/ Charts/ Settings/); the annotated tree is in
docs/architecture.md. Everything else root-level is build plumbing (csproj, slnx, bin/, obj/,
FodyWeavers.xml); assets/ holds logo.* and the README banner. All files share the single
task_monitor namespace — folders are physical only, so XAML x:Class/xmlns:local never
reference a folder.
App.xaml under src/ needs the explicit <Page Remove> + <ApplicationDefinition Include>
pair in the csproj — it must stay, or the build loses Main.
Adding a metric: new XxxSampler (Sampling/) + XxxDetailView (UI/Details/), wired into
SystemSampler.Sample / DetailWindow.ShowColumn — plus a Mask* bit + settings.yaml key + a 采样
card for its sampling switch (SettingsExpander only if it has sub-settings — CPU/内存 are plain
cards), and one row in docs/settings-plumbing.md.
All WPF UI targets Fluent 2; before adding any control/layout/icon/interaction, first check
whether iNKORE.UI.WPF.Modern ships a component for it. Local checkouts (use these first):
D:\ai-ref\UI.WPF.Modern (source + samples), D:\ai-ref\Documentation. The project tracks NuGet
0.10.2.1 and the checkout matches — beware version drift. Quirks, each documented at its site:
TabControl's strip "+" and per-tab "×" default VISIBLE; plain switchers use
TabControlPivotStyle/TabItemPivotStyle (the default is an opaque TabView lookalike, wrong on
acrylic); the Pivot hit-test flaw (invisible PreviousButton swallows first-tab clicks) needs
PivotNavButtonFix.Apply.
Window families — never mix iNKORE's UseModernWindowStyle/SystemBackdropType with
FluentWpfCore on the same window: DetailWindow = borderless FluentWpfCore acrylic (Win10
carve-out: no material, AllowsTransparency layered window + self-drawn rounded tint card — §40);
SettingsWindow = iNKORE modern window + Mica; ConsentDialog/UpdateAvailableDialog = iNKORE modern
window (plain). iNKORE also styles the taskbar right-click menu
(standard ContextMenu via SetResourceReference, by key), not MenuFlyout (can't place at the
cursor; NREs unless owned).
The "don't regress" index. Full unwinding of every entry — the incident, the measurements, the
mechanism — is docs/gotchas.md, same numbering; each rule's canonical
statement is a comment at the code site named there. Companion: settings-plumbing.md,
scroll-stack.md.
Taskbar / boot
- Classical band is ours to restore —
RestoreMinWindowbefore ANY re-measure of our own, inWM_DESTROY, on an orientation flip, and on exit. - Boot/explorer-restart:
Start(target)polls its own taskbar; ANY return → the manager re-resolves and re-enters after 2s; cancelled entries never revive; the WndProc delegate stays process-lifetimestatic readonly. - Nothing escapes
WndProc— per-message try/catch →CrashReporter; an escaping managed exception is0xC000041D. - Global crashes = log file FIRST, then a code-only
CrashDialog; a fatal report blocks the dying thread on it. Logger—logs/task_monitor-<date>.log, 7-day retention, never throws; hot per-tick paths never log (WarnOnce). Keep the line truthful.- The overlay must never steal focus — full no-activate set, incl.
SWP_NOACTIVATEon everySetWindowPos; sole exception: 悬浮模式 with 置顶显示 OFF (§36). SetWindowPos(HWND_TOPMOST)no-ops when not foreground — activate first;EnsureTopmostverifies the exstyle bit.- Acrylic — FluentWpfCore
UseWindowComposition=Trueon every WPF window, never hand-rolled Accent P/Invoke (carve-out: DetailWindow on Win10, §40); the native floating overlay has NO material (§36); the menu host needsWS_EX_TOOLWINDOW. - 竖直任务栏与任务栏族无关 — orientation comes ONLY from the target rect's aspect, never
classical; always use the column grid + tray/top anchor. Thickness selects two-line strip / one-line column / multi-column (ComputeVerticalLayoutagainst BAND width, never the rounded window). SetParent嵌入任务栏偶发 err=87(跨线程瞬态) — retry in place (5×250ms), judge byGetAncestor(GA_PARENT), and a failed embed DESTROYS the round (never fall through top-level in parent-relative coords → 屏幕左上角滞留); drain the teardown's WM_QUIT before returning; the tick holds the embed invariant.- 多显示器 = one SamplingController + N overlays — persist monitor interface IDs, never indices/HWNDs; each selected target has its own STA/RenderState/taskbar and cancellable lifecycle, never its own sampler. DWrite = per-HWND ISOLATED factory rooted in RenderState; failed Start aborts cleanly. Detail data comes from controller, placement from CLICKED overlay; floating stays singleton. Full rules: gotchas §46.
- Native overlay STAs are PerMonitorV2; resize must reach the HWND — compare X/Y + width/height; floating derives DPI/height independently of the primary band; docked hide/peek keeps home DPI (real drag/undock follows live); update region in the same pass. Back-buffer-only resize clips the card.
Samplers / metrics
NetSamplernever enumerates NICs per tick (~275ms = ~97% of idle CPU).- Live CPU speed = PDH
% Processor Performance× base clock, notCallNtPowerInformation. - Per-process CPU/RAM/disk = ONE
NtQuerySystemInformationwalk; the disk column is the 24H2+ trailer, NOT SRUM. - Disk/GPU replicate Taskmgr exactly (
IOCTL_DISK_PERFORMANCE/ DXCore COM); disk handles are opened and closed per query, never held. On Win10 fetch the baseIDXCoreAdapter+ all four attribute passes; capability gates — never OS-version checks — drive the fallbacks. - Per-process GPU% is PDH, not DXCore — encoding and aggregation at
ProcessGpuSampler.cs; no elevation, unlike SRUM. - Process-row tooltip = view-owned
PopuponMouseMove, never a rowToolTip. - Per-process net = SRUM real-time API (why we elevate).
SamplingControllerowns the only registration; never unregister on overlay teardown. Failed SRUM/poller cleanup retries ≤3, then BLOCKS rebuild — never accumulate registrations or retire the callback root early. - Wi-Fi
wlanapiis on-demand only — never on the per-tick path. - Memory breakdown =
GlobalMemoryStatusEx+ NTQSI class-2/class-80, notGetPerformanceInfo. - Charts are hand-drawn WPF — hover =
HitTest+ a realPopup. - Scroll stack —
Style="{DynamicResource {x:Type ScrollViewer}}"on every scroller, or it silently falls back to the Aero bar. - Idle GC at event points only — no timer/burst,
WaitForPendingFinalizers, or working-set trim (TrimMemory: 30 s limit, one plainGC.Collect). - 开机自启动 = a logon scheduled task, never the Run key (XML-registered, not schtasks switches).
UI / theme / settings
- 深浅色 =
ThemeManager.Current.ApplicationTheme; neverGetActualTheme(window)on DetailWindow; the native overlay tracks the system theme separately. - Never
<StaticResource …/>-alias a theme brush inWindow.Resources— it freezes the startup scheme. - 采样间隔 is runtime state — rate samplers normalize over REAL elapsed time, never "1 tick = 1s".
- Sampling mask ≠ pinned-window click mask — unpinning must not re-enable a sampling-disabled slot (toggle posts
WM_APP_SET_METRICS; enable primes baselines). - 合并相同程序 merges BEFORE the top-8 cut; svchost.exe is exempt.
- svchost→服务命名 runs AFTER the merge; the hover 描述 is resolved lazily and cached per service.
- 磁盘/GPU 显示方式 — the mode picks only the headline; every device is queried each tick, and a mode/index change clears the history.
- 网络适配器 — the virtual-adapter filter does NOT apply to an explicit pin; absent/down → 自动, re-probed every 30 ticks.
- 公网 IP off kills ALL public traffic (lookups + the ICMP probe); the LAN gateway ping is unaffected.
- Clash/Mihomo —
req.Proxy = null; rows appended standalone and exempt from merging;nullendpoint = poller asleep. - DISPOSE back-buffer wrappers before
ResizeBuffers— they live onRenderState, never in locals. - 更新检测 — csproj
<Version>is the only version source; CNB reads the WEB 307 redirect; 不再提醒 skips only that version; an unreachable host (not an HTTP answer) earns ONE retry 5 minutes later, and it re-reads the live config. - net48's
Run.Textis not a dependency property — a{Binding}on aRunthrows at startup; set named Runs in code.
Floating widget / rendering / forensics
- A lost D3D device is recoverable, never a crash — only the raw
EndDraw/Present/ResizeBuffersHRESULTs can tell (DirectN's helpers lose the code as E_FAIL): latchRenderState.DeviceLost, rebuild in place, release DComp withMarshal.FinalReleaseComObject. - 悬浮模式始终单实例 — no
SetParent; self-drawn rounded card, no material/frame; the drag moves the pair, the dodge separates it. Home uses independent validity (negative coords valid); ordinary clamp runs only when out-of-bounds is OFF. Read gotchas §36 first. - A native crash is invisible to managed handlers —
CrashTrace's VEH is the only record; don't add a WndProc handler for one. Crashless stalls → per-threadCheckOverlayHealth, trace slots andSystemSampler.Timed. - 贴边隐藏 — home owns dock + clip, slide owns position — ≤8 DIP (左/右/上); home point selects monitor; clip region to home work area, unclip while pressed; re-hide only when the cursor is outside the visible clip. Ordinary clamp depends on out-of-bounds; slides/held buttons suspend reposition; slides never write home.
- 全屏时隐藏 — probe owns VISIBILITY, never position — per-tick, DWM extended-frame bounds (MAXIMIZED never counts);
SW_HIDE/SW_SHOWNOACTIVATEonly; tick, heartbeat and sampling keep running; held button defers hiding; dodge suppressed while hidden. - Win10 的 DetailWindow 没有亚克力 —
MaterialMode=None+AllowsTransparency分层窗口 +RootBorder自绘圆角卡片;ShowColumn末尾补InvalidateVisual+SWP_FRAMECHANGED。 - Prewarm 永不抛出,
_stopping时跳过;启动时在 mutex 之后清残留 sentinel。 - 经典任务栏(Win10)下覆盖层自画不透明底色 — 被
WS_CLIPCHILDREN冻住的残影擦不掉、只能遮盖(CardBrushalpha 恒 1 全幅填充);底色每 tick 采样SampleTaskbarBackdropColor(3-tick 稳定期);Win11 保持全透明。 - 窗口死后必须退出消息循环;僵尸要自愈 — 裸
WM_NCDESTROY也 teardown+quit;foreign calls 走有界 worker(SetParentinline);entry 清理 worker/trace;watchdog 5s 唤醒、45s 重启。 - UAC/锁屏后 WPF 不渲染 = 多窗口 BitmapCache 缺陷 — 首个窗口前设
ShadowAssist.UseBitmapCache=false;触屏临时缓存也走此开关,禁止单独恢复。
- iNKORE.UI.WPF.Modern: the local checkouts above; github.com/iNKORE-NET/UI.WPF.Modern.
- SRU real-time API: reversed from
Taskmgr.exeinC:\Users\l\SRUM-RealTime-API.md— the canonical reference forProcessNetSampler/SrumInterop.
Keep it in sync with the code — update it in the same change, not later. Stale guidance is worse than none; fix or delete any claim that no longer matches the code on the spot.
每次更新本文件前,先检查它的体积,整个文件控制在 20 KB 以内。 加入内容后将超过 20 KB 时, 要想清楚是否真的需要加入;必须加入的话,精简语序,并考虑是否需要优化(压缩或迁移到 docs/)已有内容。
Route each fact to exactly one place, and point at it elsewhere (a fact held in two prose copies WILL drift — it already happened once, to the 合并相同程序 wording):
| The fact is… | Goes in |
|---|---|
| a rule an agent must not break, expressible in 1–2 lines | the gotcha index above |
| the reasoning, incident history, or multi-step mechanism behind that rule | docs/gotchas.md |
| the cross-file design (threads, contract, detail/pin model, startup, layout) | docs/architecture.md |
| how a 设置 item reaches its sampler | docs/settings-plumbing.md |
| the design of one module used from many places | a topic doc in docs/ (e.g. scroll-stack.md) |
| derivation, calibration constants, offsets, reversed layouts | the code site's own comment — link to it, never copy it |
| what the code already says plainly (signature, field name, obvious branch) | nowhere |
New gotcha → a numbered line here + a matching ## N. section in docs/gotchas.md.