Win release artifacts now live in their own Azure Blob container
(heicodeblob/win/) — Manager download page and Tauri auto-updater both
point at win/updater/win-latest.json. Mac stays on the existing custom
sha256-verified mac-latest.json until the Mac CI/CD pipeline migrates
to Tauri minisign format.
Also fixes the in-app balance pill: /v1/dashboard/billing/{subscription,usage}
on the Manager accepts the `sk-` channel token, /api/user/self does not.
Switched the proxy in heicode-auth to use the billing endpoints so the
BalanceBar actually renders real remaining/used quota.
- cc-haha/desktop/src-tauri/tauri.conf.json: updater endpoint → win container
- cc-haha/src/server/api/heicode-auth.ts: balance via /v1/dashboard/billing
- cc-haha/desktop version bump 0.2.4 → 0.2.5 (next Win release)
- cc-haha/.gitignore: exclude installer artifacts (msi, dmg, sig, …)
- heicode/controller/desktop_download.go: dual-feed (Win Tauri + Mac custom)
- Guard installUpdate() with isDownloading flag to prevent concurrent downloads
- Deduplicate checkForUpdates() with checkInProgress promise
- Track monotonic progress (peakPercent never decreases)
- Reduce startup check delay from 5s to 1s
- Bump desktop to 0.2.4
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
appendAssistantTextMessage was merging consecutive assistant_text messages
regardless of whether they belonged to different conversation turns. Added
turnComplete flag that gets set on message_complete, error, and status(idle)
events, preventing subsequent turns from merging into the previous response.
When user clicks the X button, the app now hides to the system tray
instead of exiting. Users can restore the window by clicking the tray
icon or selecting "显示 Heicode" from the tray menu. To fully quit,
use "退出 Heicode" from the tray menu.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Replace all encoding/json direct calls with common.Marshal/Unmarshal/DecodeJson per Rule 1
- Fix Dify nil pointer dereference on remote image upload (relay-dify.go)
- Fix Claude relay file content type detection for text/* and PDF (relay-claude.go)
- Fix unsafe type assertions in Claude relay and Vertex GetModelRegion
- Fix StreamScanner unconditionally resetting pre-existing StreamStatus
- Add inferMimeTypeFromFilename() for proper MIME type handling in DTO
- Fix Mac build script hardcoded DMG version (now reads from tauri.conf.json)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Bump version to 0.2.1 across tauri.conf.json, package.json,
Cargo.toml, updater manifest, and Mac fallback URL.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
On Windows, Tauri returns paths with backslash separators. Multiple
components used .split('/') to extract filenames/segments, which
returned the entire path as a single element on Windows. Changed all
instances to .split(/[/\]/) to handle both Unix and Windows paths.
Affected: ProjectContextChip, ToolCallBlock, PermissionDialog,
ToolCallGroup, FileSearchMenu, InlineImageGallery,
LocalSlashCommandPanel, ProjectFilter, StatusBar, DirectoryPicker.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Same bug as ChatInput — inserting filename at cursor without removing
the @filter trigger text, producing @foofile.ts instead of file.ts.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The onSelect handler inserted the filename at cursor position without
removing the @filter trigger text, producing "@foofilename.ts" instead
of replacing the whole "@foo" with "filename.ts".
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Guard empty thinking events from creating blank ThinkingBlock rows
- Skip empty assistant_text from history loading
- Hide ToolResultBlock when content is empty (non-error)
- Add component-level null returns as safety net
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Fix CJK font rendering: add PingFang SC, Microsoft YaHei, Noto Sans CJK SC
fallbacks to all CSS font stacks (headline, body, label, mono)
- Clear docs_link default (was pointing to upstream docs)
- Remove user-facing "NewAPI" text from en/zh i18n strings
- Bump desktop version to 0.2.0
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Balance pill was rendering nothing for admin/root accounts because
mcp-server's §4 returns HEICODE_USER_NOT_FOUND for users that never
came through from-agnet onboarding. Fall back to Heicode NewAPI's
own /api/user/self when that happens; reshape into the same envelope
so the UI is path-agnostic.
New balanceStore (zustand) — single polling loop, BalanceBar +
avatar ring share it. AppShell starts it once auth bootstraps.
BalanceBar now shows a "loading…" placeholder on first fetch so
the widget is visible from frame one.
SidebarUserCard avatar wears an SVG ring whose arc length tracks
remaining/(remaining+used) and color hits the same green→amber→red
thresholds as the bar.
Manager: /desktop-client drops the manifest-notes wall of text, the
old HEICODE_DESKTOP_FILE_* subtitle goes away, and a Mac fallback
entry is always spliced in when the live manifest is Win-only.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Today's 0.1.6/0.1.7/0.1.8 all built but never shipped a manifest
(some were superseded mid-iteration; 0.1.8 had Win binary on blob
but Mac wasn't ready). 0.1.9 ships the full stack as one release:
- BalanceBar under the composer now has a real progress bar (was
text only). Fill = remaining/(remaining+used); color shifts
green → amber → red below 30%/10%.
- (from 0.1.8) AskUserQuestion early-return moved below all hooks
so the render order is stable across input mutations.
- (from 0.1.8) chatStore content_delta throttle is now per-session
(Map<sessionId, {pending, timer}>); no more cross-tab text bleed.
- (from 0.1.8) endpoints array trimmed to blob-only — SWA URL gone
so a fallback failure no longer flashes a third-party domain.
- (from 0.1.7) new app icon — already on disk in icons/ + public/.
- (from 0.1.6) Manager desktop_download.go reads blob manifest so
the /desktop-client page tracks releases without env wrangling.
- Build pipeline: `tauri build --bundles nsis` is the release path
(skips MSI/WiX, ~2-3 min/build saved). sccache wired into
~/.cargo/config.toml; next build is the first with warm cache.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
P0: AskUserQuestion.tsx had `if (questions.length === 0) return null`
sitting between the `useState` calls and the `useMemo` calls below.
On any render where parseInput(input) flipped between empty and
non-empty (e.g. streaming permission_request input mutates) React
threw "Rendered more hooks than during the previous render". Moved
the early return after every hook call.
P1: chatStore.ts had `pendingDelta` + `flushTimer` at module scope,
shared across every active session. When two sessions streamed at
the same time (e.g. user has a team-member tab open alongside their
own), session B's content_delta would queue onto the same module
buffer as session A; whichever flush timer fired first emptied the
buffer into its own session. Result: text leaked between
conversations.
Replaced with a `Map<sessionId, { pending, timer }>` so each stream
owns its own throttle buffer. `consumePendingDelta(sessionId)` and
`dropBuffer(sessionId)` keep the API similar to before; nine
callsites updated.
Both issues caught by the bug-audit agent run after 0.1.7 build.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Visual brand refresh: replaced the desktop client + Manager web logos
with the new glossy gradient H glyph.
Desktop (cc-haha):
- src-tauri/icons/*: regenerated via `tauri icon` from new-icon.png
(center-cropped 1024 square). Updates icon.ico/icns/png + 8 Windows
Square*Logo sizes + iOS + Android mipmaps.
- public/app-icon.png + app-icon.svg: replaced. SVG is now a wrapper
around the embedded PNG so existing <img src="…app-icon.svg"> refs
keep working without recoloring tooling.
- Version bumped to 0.1.7 across tauri.conf.json, package.json,
Cargo.toml.
Manager web (heicode/web/default):
- public/logo.png: 256x256 of the new glyph.
- public/favicon.ico: multi-size ico (16/32/48/64/128/256).
- public/heicode-logo.svg: same SVG-wraps-PNG trick as desktop.
Backend (heicode/controller/desktop_download.go):
- Already redeployed earlier today — the Manager web "Heicode 桌面客户端"
page now sources its download URLs from the Azure Blob updater manifest
instead of VM-local files. This is the fix for "still downloading
Heicode_0.1.0_windows_x64_msi.msi"; the page will reflect 0.1.7 the
moment the manifest publishes below.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
cc-haha desktop (0.1.6):
- New sidecar route `/api/heicode-auth/balance` proxies mcp-server
§4 `/api/user/heicode/balance` using the active provider's stored
access token. Returns null silently on 401 / network failure so
the UI doesn't flash error strips.
- New BalanceBar component renders a compact pill right below the
ChatInput: `[wallet icon] $X.XX 剩余 · 已用 $Y.YY · N 次`. Polls
every 60s. Hidden when not logged in.
- Quota → USD display uses NewAPI convention (500_000 units = $1).
heicode Manager (Go controller):
- `GetDesktopDownloads` rewritten to pull from the same Azure Blob
updater manifest the in-app updater uses (`heicodeblob/.../
updater/latest.json`). 5-min in-process cache; stale-on-error
fallback. Stops the Manager web from showing stale
`Heicode_0.1.0_x64-setup.exe` after fresh releases.
- `DownloadDesktopFile` kept for back-compat — it now 302s to the
manifest's blob URL instead of streaming a VM-local file.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Per product-package design docs, IM integration (Lark / Feishu /
Telegram) is not a Heicode surface. Hide UI without ripping out
plumbing — keep AdapterSettings.tsx + adapterStore + types in the
source tree so re-enabling is a one-flag flip.
- Settings.tsx: drop the "IM 接入" tab button (`'adapters'` route
still resolves internally if someone forces it, but no nav).
- NewTaskModal.tsx: remove the IM notification channel pickers
(Feishu / Telegram checkboxes + "no channel configured" warning).
Existing task `notification` config is preserved on edit; it's
just no longer mutable from this UI.
- Unused imports (useEffect, useAdapterStore) cleaned to satisfy
TS strict mode.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Sidebar search row gains a recency-filter chip (All / 1d / 3d /
7d). Active filter highlights brand color; selection persists in
localStorage (`cc-haha.sidebar.timeFilter`). Default `all` keeps
current behavior.
- SidebarUserCard becomes a button — clicking the avatar/name pill
opens https://code.xinghanlab.com/ in the system browser via
@tauri-apps/plugin-shell, with window.open fallback for dev builds.
Collapsed-sidebar avatar circle also clickable.
- i18n: `sidebar.timeFilter.title/all` + `sidebar.userCard.openProfile`
added in zh + en.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- updateStore: arm a 2-hour periodic checkForUpdates after the
startup pass so users who keep the client open for days still
see new releases without restarting. Idempotent, silent — only
the top-right popup surface fires.
- Sidebar: bucket session history into Past 24h / Past 3 days /
Past 7 days / All older (was Today/Yesterday/7d/30d/Older).
Rolling windows from `Date.now()` instead of calendar bounds.
- i18n: replace `today/yesterday/last7days/last30days` keys with
`within1day/within3days/within7days`; ScheduledTasksList and
ScheduledTasksEmpty migrated to the new keys.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Roll-up of tonight's desktop fixes shipped as 0.1.1:
- PermissionDialog: cap diff/command preview at max-h-360 so the
allow/deny buttons stay above the fold on huge writes (user
reported scrolling 900 lines to find the buttons).
- SessionTaskBar + cliTaskStore: hover row shows ✓ manual-close
button for tasks the agent forgot to mark completed.
- AskUserQuestion: honor schema's `multiSelect: true` — toggle
membership across options, switch round → square indicator,
show "可多选" hint, join answers with comma.
- HeicodeLoginPage: removed dead `'official'` filter that blocked
the typescript build (legacy provider id no longer in the union).
- Settings About: removed the third-party social-media block and
unused openUrl helper.
- i18n: replaced misleading "GitHub Releases" wording with
"Heicode 官方更新源" / "Heicode update source" — actual channel
is Azure Blob (msi/updater/latest.json) per tauri.conf.json.
- release-desktop.mjs: az invocations now run with shell:true so
the Windows `az.cmd` resolves; also uploads the manifest to
blob (primary endpoint) as the script finishes.
- tauri.conf.json + package.json + Cargo.toml: version bumped to
0.1.1.
- website/public/updater/latest.json: now reflects 0.1.1 + new
signed NSIS URL (mirrored to blob by the release script).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- pubkey: rotated to the keypair stored at
C:\Users\陈晨\.heicode-updater\heicode_updater.key (private side
is the user's; only the pubkey ships in tauri.conf.json).
- scripts/release-desktop.mjs: one-shot release helper —
uploads the signed bundle artifacts to Azure Blob (account
heicodeblob, container msi, public-blob-read) and rewrites
website/public/updater/latest.json to point at the new URLs.
- UPDATER.md: rewritten with the concrete URLs, container, key
paths, and step-by-step commands. No more generic placeholders.
Azure Blob setup (done out-of-band, not in this commit):
- Storage account heicodeblob set allowBlobPublicAccess=true
- Container msi set to public-blob read
- Smoke-tested: https://heicodeblob.blob.core.windows.net/msi/<x>
returns 200 anonymously.
The actual Azure connection string + private-key path live in
scripts/.env.release, which is gitignored under .env.* and was
verified excluded before this commit.
NOTE: pubkey was rotated. Any MSI already in the wild signed by
the *previous* key cannot self-update to this signing chain —
those users need a fresh manual install. This is acceptable for
pre-GA where no public release exists yet.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- tauri.conf.json: endpoints now points at the website-hosted
manifest (https://<azure-swa>/updater/latest.json) so the
already-built UpdateChecker actually has somewhere to check.
- website/public/updater/latest.json: placeholder manifest with
empty platforms map (clients will see "up to date" until a real
release ships).
- scripts/build-updater-manifest.mjs: helper that ingests signed
Tauri bundle artifacts and emits the manifest, so future releases
are one node command instead of hand-edited JSON.
- scripts/UPDATER.md: step-by-step for every release — what to set,
what to upload, where to commit.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replace the random spinner verb with a friendly description of the
tool currently running — "Reading foo.ts", "Editing bar.tsx",
"Running npm", "Searching <pattern>". Falls back to the verb / chatState
label when no tool is active.
Why: users were seeing "Percolating... 51s" with no idea whether the
agent was stuck or genuinely working. The chatStore already tracks
activeToolName + streamingToolInput, the indicator just wasn't using
either.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Five gaps closed against the updated product-package spec
(http://gitee.ath.cx:3000/xiaohei/heicode/src/branch/main/docs/product-package):
P1. /dashboard hero rewritten per §10 §"初始首页"
features/dashboard/components/home-hero.tsx replaces the technical
CockpitView with four blocks the spec mandates: 主输入 / 继续任务 /
今日焦点 / 辅助入口. Main input is "你想把什么想法变成可以上线的软件?".
Submit only stashes the idea to localStorage + toast — the actual task
conversation belongs in the desktop client per §13 §5.1.
P2. /tasks/$id TaskCard route per §10 §"任务卡" + §11 §3
features/tasks/task-card-view.tsx renders one AgnetDeployment as the
user-facing task object: 目标 / 第一版范围 / 自动生成 / 待确认上下文 +
Manager 辅助按钮. Linked from Home hero's 继续任务 list.
P3. /sk-sources 推荐摘要 dialog per §10 §"推荐确认卡"
features/agnet-console/pages.tsx RecommendationSummaryDialog. Five
blocks (本次会做 / 本次允许使用 / 本次不会做 / 高危规则 / 预计消耗) +
Launch Agnet button with "参数由 Heicode 自动生成" caption. No JSON
editor, no permission manifest — §10 高级展开禁令.
P4. /audit redacted card view per §10 §"任务用量与审计" + §6
features/agnet-console/pages.tsx AgnetAuditPage. Old裸 table replaced
with脱敏 cards exposing only the fields docs allows: resource_id /
resource_type / allowed_actions / constraints / secret_ref. Helper
function maskIfSecret() catches any stray plaintext credential the
backend might leak. Banner says explicitly "明文密钥从不展示".
P5. Login screen filters Claude Official provider per §8
cc-haha/desktop/src/components/login/HeicodeLoginPage.tsx hides the
legacy `official` preset so the login carries Heicode brand alone.
i18n fix (BIG):
i18next defaults to `defaultNS = 'translation'`. The earlier custom
keys had been written to the JSON root, NOT into translation, so
every t('Preparation checklist') was returning the English key as
fallback all along. Moved 67 orphan keys (zh+en, both files) into
the translation namespace where they're actually resolvable. Verified
by loading i18next + zh.json in bun and confirming all keys resolve
to the expected Chinese strings.
Cache-busting from earlier session (already deployed via SFTP, never
committed): index.html / constants.ts / footer.tsx now hold the
?v=h-glass-2 suffixed asset URLs in git, so future docker rebuilds
preserve them.
Per user directive: tested locally only (TS check clean, i18next
resolves correctly). NOT deploying to the VM in this commit — user
asked to keep production untouched until they verify the changes.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The earlier UserPill landed in the TitleBar tab strip, but the main
desktop layout (HeicodeTasksHome shell) renders its own chrome without
that strip — so the user info ended up invisible despite mcpAuth
having the right data after a2deeb6. Pull the same identity (avatar
gradient + name + email + role badge) straight into the bottom of
Sidebar.tsx, right above the logout/settings rows, so it's visible
on every screen regardless of which top bar is mounted.
Two layouts:
- expanded: 36px gradient avatar + name + email + admin/root badge
- collapsed: 36px avatar circle only, title tooltip carries the email
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Beyond the i18n strings rebrand (b55d103) this pass cleans up everything
that still surfaced "Claude" in the built artifact:
Theme tokens (cc-haha/desktop/src/theme/globals.css)
- light-mode text-selection-bg rgba(197,165,114,*) → rgba(123,107,227,*)
(selecting ANY text in light mode used to paint a gold highlight)
- dark + light diff-highlight-bg / -gutter switched from gold to brand
violet rgba(123,107,227,*) — keeps semantic "highlight" without leaving
the brand
Sidecar binary rename: claude-sidecar → heicode-sidecar
- desktop/sidecars/claude-sidecar.ts renamed (git mv)
- internal log prefix strings (12 occurrences) updated
- desktop/scripts/build-sidecars.ts: entrypoint / outfileBase / productName
('Heicode Sidecar') / publisher ('Heicode') — these last two are embedded
in the .exe metadata you see in File Properties
- src-tauri/capabilities/default.json: 9 binaries/claude-sidecar references
rewritten + description "Default capabilities for Heicode Desktop"
- src-tauri/src/lib.rs: 2 .sidecar("claude-sidecar") + 3 packaged sidecar
exe filename strings + 1 doc comment
- desktop/sidecars/launcherRouting{,.test}.ts
After this rebuild, Task Manager shows heicode-sidecar-*.exe child
processes (was claude-sidecar-*.exe) and right-click → Properties on
that binary shows "Heicode Sidecar" / "Heicode" instead of "Claude Code".
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
[data-theme=light] block in globals.css still defined --color-primary as
#8E7547 with gold gradient buttons and gold focus rings. Dark mode was
correctly on the brand violet #7B6BE3 since the Iris overhaul, but the
moment a user switched to (or was OS-defaulted to) light theme they got
the legacy gold mark on login button, focus ring, sidebar active state,
shadow-button-primary, etc.
Rewrote the light override to mirror the dark brand palette:
- --color-primary #7B6BE3, primary-container #5B4FB8, fixed #9A8DEC
- --color-secondary #6B7CE0 (was slate #5A6B82)
- --gradient-btn-primary 3-stop violet→blue (matches dark mode + Manager)
- --gradient-brand-wordmark also added in light scope (was inheriting
but explicit avoids cascade surprises)
- All rgba(142,117,71,*) shadows / borders rewritten to rgba(123,107,227,*)
Code-syntax / diff highlight golds left alone (those tint code text, not
brand UI).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Wave 1 was design tokens + login screen. Visible-but-subtle. Wave 2
ships the meaningful in-app changes a user actually sees after they
log in. Aggressive enough that the post-login surface feels like a
2024-vintage product, not a 2018 utility.
User chat bubble (src/components/chat/UserMessage.tsx)
- Background: violet-tinted vertical gradient
(rgba(123,107,227,0.10 → 0.04)) over surface-user-msg
- 1px violet-18% border + soft violet-8% drop shadow
- Asymmetric corner (18/4/18/18) preserved
- Effect: user messages read distinctly from assistant messages
at a glance, no need to look at the avatar.
Assistant chat bubble (src/components/chat/AssistantMessage.tsx)
- Top-down white wash (rgba 0.025 → 0) on container surface
- Two-stop shadow (1px ambient + 4px lift) so the bubble lifts
off the page instead of disappearing into the chat background
- Border tightened to full --color-border (was 60% opacity)
Glass-panel composer surface (src/theme/globals.css)
- Top violet wash (5% → 0% over 35%) over glass surface
- Inset top hairline (white 4%) for lit-from-above feel
- Hover: violet-18% border tint
- Focus-within: violet-45% border + 3px violet-18% halo + dropdown
shadow, so typing feels obviously "on"
- Smooth 200ms transitions on border + shadow
Sidebar active session (src/components/layout/Sidebar.tsx)
- Left edge: 0.5px × 20px violet→blue gradient rail with 8px
violet glow — instant focus anchor
- Active dot: 8px violet glow shadow
- Active row: 1px violet-24% border + inset top hairline
- Inactive: hover lifts text to primary (was secondary→primary on
hover, now also adds violet-6% bg tint via token change)
Tasks home intent box + recent task cards + workspace reply box
(src/pages/HeicodeTasksHome.tsx)
- Intent box: violet wash gradient + transition + hover/focus
border tint
- Recent task cards: hover -translate-y-px lift + violet-32%
border + violet-10% drop shadow → clearly clickable
- Workspace reply box: same violet wash + focus halo
Type-check: bunx tsc -b --noEmit clean.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
#1 Login wordmark color
The big "HEICODE" wordmark on the login page was rendered in the
gold brand color (#C5A572), which clashed visually with the
purple→blue glassy H app icon shown in the window/taskbar/dock.
Now the wordmark uses the same linear-gradient(135deg, #B888E5,
#6B7CE0) as the logo so the login surface matches the icon.
Backdrop radial glow tinted to match. Other surfaces keep gold
primary so buttons/accents stay consistent app-wide.
#3 User pill in TitleBar
Logged-in users had no in-app affordance to confirm "I'm signed
in as X" or to know which account they're using. Add a pill on
the right of TitleBar showing initial avatar (logo gradient) +
display name + admin/root role badge.
Wiring:
- cc-haha/src/server/types/provider.ts: McpAuth gains optional
email / displayName / role fields. Backwards-compatible (all
optional, existing saved providers stay valid).
- cc-haha/src/server/api/heicode-auth.ts: handleLoginWithCredentials
now reads user.{name|display_name, email, role} from the
/api/auth/login response and persists them on the provider's
mcpAuth record. /api/heicode-auth/status returns a `user` field
synthesized from the active provider.
- cc-haha/desktop/src/api/heicodeAuth.ts: HeicodeAuthStatus type
gains the `user` shape.
- cc-haha/desktop/src/components/layout/TitleBar.tsx: new UserPill
component reads from useHeicodeAuthStore; hidden until status
loads so the bar doesn't flicker on boot.
Type-check: bunx tsc -b --noEmit clean.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Tauri's macOS bundler validates that every PNG referenced from
src-tauri/icons/ is RGBA. Our previous rebuild-icons.py wrote RGB PNGs
because the trim/pad pipeline lives in RGB to keep transparent edges
out of the glassy logo. macOS build failed with:
error: proc macro panicked
--> src/lib.rs:1073:16
message: icon /.../desktop/src-tauri/icons/32x32.png is not RGBA
Fix: scripts/rebuild-icons.py now does `.convert("RGBA")` on every
PNG output before saving. Alpha is fully opaque (255) — the rounded
glass edges retain their look on any background since we paint on
solid white in load_trimmed_square. Same files keep working for
Windows .ico embedding (which accepts RGBA fine).
Regenerate all 17 PNG outputs (32/64/128/128@2x/icon.png + 9 Square*
+ StoreLogo + public/app-icon.png) and the derived .ico/.icns.
Mac side: pull this commit and re-run `bun run build:macos-arm64`.
Windows side: no change in behavior (Tauri's NSIS bundler doesn't
care about PNG color mode).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
ModelSelector and PermissionMode changes trigger a server-side CLI
restart (handler.ts:521 restartSessionWithRuntimeConfig). The server
emits status:'thinking' with verb 'Switching provider and model...' or
'Restarting session...' to keep the spinner spinning during the ~3s
restart window. ChatInput's isActive=`chatState !== 'idle' && hasMessages`
guard let the run button flash to a red 「stop」 affordance during that
window for any session with prior messages — confusing because hitting
stop has nothing to interrupt.
Add a statusVerb prefix check so isActive stays false during these
system transitions:
isSystemRestartTransition =
statusVerb.startsWith('Switching provider and model') ||
statusVerb.startsWith('Restarting session')
isActive = chatState !== 'idle' && hasMessages && !isSystemRestartTransition
Match by prefix so any future suffix (e.g. " (CLI starting...)") still
trips the guard.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
#1 — Untitled session can't be deleted
src/server/services/sessionService.ts:deleteSession is now idempotent.
Previously placeholder sessions whose JSONL file hadn't been flushed
yet would 404 the delete and stay stuck in the sidebar list. Now we
succeed silently if the file's gone (and treat ENOENT during unlink
the same way), matching how the frontend already optimistically
removes the row.
#2 — Run button turns red 「stop」 on idle when switching model
desktop/src/components/chat/ChatInput.tsx — gate isActive on
hasMessages. ModelSelector / runtime config changes briefly flip
chatState off-idle (CLI reconnect / startup). Without messages
there's nothing to stop, so the button should stay disabled
gradient, not turn into a red stop affordance.
#3 — Draft input bleeds across session switches
desktop/src/components/chat/ChatInput.tsx — ChatInput is mounted
once at app shell level; switching tabs doesn't re-mount it, so
the local `input` useState carried over. Add a useEffect keyed on
activeTabId that resets input + attachments + open menus + filter
buffers. composerPrefill path (rewind) keeps owning its own reset
via the existing prefill effect.
#4 — Close (×) minimized to tray instead of quitting
desktop/src-tauri/src/lib.rs — drop the prevent_close + hide
pathway on the main window's CloseRequested. Close now actually
quits; users who want to keep the app running can minimize via
the existing window controls. Tray icon stays available for
re-open + explicit quit.
#5 — Tray menu hardcoded "Claude Code Haha"
desktop/src-tauri/src/lib.rs — rename tray menu items, tray
tooltip, and macOS app submenu to "Heicode" / "显示 Heicode" /
"退出 Heicode" / "关于 Heicode".
#6 — Skills page silently empty when one source crashes
src/server/api/skills.ts:listSkills uses Promise.allSettled so a
single failed source (user / project / plugin) returns a partial
list + structured errors[] instead of tanking the whole response.
desktop/src/api/skills.ts + stores/skillStore.ts thread the
errors through; SkillList only shows the hard-error wall when
skills.length === 0.
Verification:
bunx tsc -b --noEmit (desktop) — clean
bunx tsc --noEmit (cc-haha root) — clean
cargo check (src-tauri) — clean
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Wire mcp-server's §7.8.3 single SSE channel (GET /api/user/events/stream,
shipped 2026-05-08 in image heicode-7.8.3-sse-v2-20260508) into the client:
cc-haha/src/server/api/heicode-tasks.ts
+ GET /api/heicode-tasks/events/stream (long-lived SSE proxy)
+ GET /api/heicode-tasks/approvals (startup pull)
+ POST /api/heicode-tasks/approvals/:id/decision
cc-haha/desktop/src/lib/heicodeEventsClient.ts (new)
EventSource wrapper with exponential backoff reconnect (1s..30s) and
a 60s heartbeat watchdog that force-reconnects on stream silence.
cc-haha/desktop/src/lib/heicodeEventsRouter.ts (new)
Routes the 5 event names (approval.requested, approval.resolved,
task.status_changed, task.execution_progress, heartbeat) into the
matching stores. Ignores unknown events so future server-side adds
don't crash the client.
cc-haha/desktop/src/stores/heicodeTaskStore.ts
+ applyStatusChange(taskId, status, caption?)
+ applySubStepProgress(taskId, subStepId, status, caption?)
cc-haha/desktop/src/main.tsx
Subscribe to auth store → start/stop the stream as loggedIn flips.
Type-check: bunx tsc -b --noEmit clean.
Bun bundle: bun build src/server/api/heicode-tasks.ts → 86 modules, 0.55 MB.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replace the email + password form on the desktop login card with a single
"一键登录 Heicode Manager" button that drives the existing OAuth bridge:
desktop click
→ POST /api/heicode-auth/oauth/start (server stages state + builds
authorize URL pointing at https://code.xinghanlab.com/heicode/oauth/authorize)
→ Tauri shell.open() the authorize URL in the system browser
→ user signs in via Manager (which now also routes /sign-in?redirect=...)
→ Manager 302s back to http://127.0.0.1:<port>/api/heicode-auth/oauth/callback?token=sk-…
→ callback handler activates the provider; status flips loggedIn=true
→ AppShell unmounts the login page
The OAuth start/callback endpoints already existed (handleOAuthStart /
handleOAuthCallback) so this is a UI-only swap; no auth-store changes.
loginWithCredentials remains exported in case we ever need a fallback,
but it's no longer wired into any UI surface.
i18n: tweak login.oauth.button to "一键登录 Heicode Manager", add
login.oauth.waiting for the polling state.
Aligns with upstream xiaohei/heicode commits 5bd8276 / e60e74b /
34a87a4 (Manager-as-only-identity) without breaking the slice 11-14
heicode-tasks proxy that still depends on the provider abstraction.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Per docs/product-package/10-frontend-detail-spec.md and the wireframes
in 11-product-prototype-wireframes.md §2-§3, the client's first-class
surface is no longer "code-companion empty state" but a Heicode task
driving cabin: input an idea, answer Heicode's follow-ups, see the
auto-generated task card, hand off to Manager for resource binding /
deployment.
This slice ships the surface as a mock skeleton — the data layer
(useHeicodeTaskStore) is seeded with two demo tasks so the wireframe
can be reviewed end-to-end before backend wiring lands.
New files:
- stores/heicodeTaskStore.ts
HeicodeTask shape (id / name / status / status_caption / thread /
card), HeicodeTaskStatus enum, ChatTurn (user | heicode), and
FollowupQuestion (with optional 'high-risk' option flag).
Actions: submitIntent, openTask, closeTask, answerFollowup,
appendMessage. Two seeded tasks ("小团队任务管理 SaaS" running
with full task card; "企业微信通知集成" awaiting approval).
- pages/HeicodeTasksHome.tsx
Two layouts behind a single route. When currentTaskId is null
we render the Home (wireframe §2):
- Header line "当前任务:未选择"
- Big intent prompt + textarea + Send (⌘/Ctrl+Enter shortcut)
- Recent tasks grid (status pill + caption + relative time)
- Manager auxiliary footer hint
When a task is open we render the Workspace (wireframe §3):
- Header with back button + task name + status pill
- Conversation thread (user bubble right-aligned, Heicode
left-aligned with "H" avatar; follow-up questions render
as chip groups with high-risk dot indicators)
- Reply textarea at bottom
- Right-side TaskCardPanel (lg breakpoint+) with goal /
scope / generated-artifacts / Manager actions / footer
buttons (修改目标 / 去 Manager 准备)
Plumbing:
- tabStore.ts: HEICODE_TASKS_TAB_ID + 'heicode_tasks' TabType,
treated like settings/scheduled in dedupe rules
- Sidebar.tsx: new "我的任务" entry between "新建会话" and
"定时任务" with a target icon
- ContentRouter.tsx: route 'heicode_tasks' → HeicodeTasksHome
- i18n: tasks.* (~24 keys per locale) + sidebar.heicodeTasks
Backend wiring TODO: when mcp-server publishes the task-orchestration
contract, swap submitIntent / answerFollowup / appendMessage for real
calls and keep the same shapes. Status updates can come in via SSE
or polling and be merged onto useHeicodeTaskStore.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Aligns the client with docs/product-package/{08,11}.md.
6a — Login surface trimmed to wireframe §1:
- Brand wordmark + tagline "从一个想法,到可上线的软件产品"
- Footer line "登录后,客户端会使用 Heicode 提供的模型。"
- ProviderLoginCard now shows ONLY: sign-in target host (read-only),
email + password, sign-in button. The local-network warning tag,
raw baseUrl pill, promo paragraph, "or via browser" alt link, and
the "RECOMMENDED" badge — all dropped per the wireframe's "登录目
标只有 Heicode" intent.
- Removed dead handleOAuth / shellOpen / isLocalBaseUrl helpers.
6b — High-risk approval dialog (08-client-guide.md §"高危审批体验"
+ 11-product-prototype-wireframes.md §9):
- New zustand store stores/approvalStore.ts with a FIFO queue of
ApprovalRequest items + decide(id, 'approve' | 'reject' |
'postpone') action. Idempotent enqueue (dedupe by id).
- New components/approval/ApprovalDialog.tsx renders queue[0] as a
modal with the 6 spec fields (task / operation / target / role /
impact / credential), a Heicode-suggestion sidebar, a risk-level
pill, and 3 actions: 拒绝 / 稍后提醒 / 批准 N 分钟. Queue depth
badge appears at the bottom when more requests are pending.
- AppShell renders <ApprovalDialog /> alongside ToastContainer so it
overlays any surface (sessions, settings, etc.).
- Backend wiring pending — for now main.tsx calls
installApprovalMock() which exposes window.__heicodeMockApproval()
for DevTools-driven demos. Real backend hook lands when
mcp-server / agent-manager publish the approval-stream contract.
i18n: added login.tagline / login.signInTarget /
login.footer.heicodeProvidesModels and a full approval.* set
(zh + en, ~17 keys per locale).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The 2026-05-08 product package (docs/product-package/) redraws the
client / Manager boundary. Per 08-client-guide.md §1-7, the client
explicitly does NOT carry resource binding, permission grants, or
any account / security surface — those move entirely to Manager.
This commit removes the client-side resources surface that landed in
slices 2-4 (commits d1db2c1, c0363be, a28c900):
Deleted:
- cc-haha/desktop/src/api/heicodeResources.ts (API client)
- cc-haha/desktop/src/stores/resourceStore.ts (zustand)
- cc-haha/desktop/src/pages/ResourceBindings.tsx (page)
- cc-haha/desktop/src/components/resources/Modals.tsx (3 modals)
- cc-haha/src/server/api/heicode-resources.ts (proxy)
Reverted:
- Sidebar.tsx: drop the Resources nav item + RESOURCES_TAB_ID import
- ContentRouter.tsx: drop the 'resources' branch + import
- tabStore.ts: drop RESOURCES_TAB_ID + 'resources' from TabType
- router.ts: drop 'heicode-resources' case + handler import
- i18n zh.ts + en.ts: strip ~63 keys (sidebar.resources +
resources.* + grants.*)
Kept (still useful for the new spec's Manager-side data needs):
- mcpAuth schema in types/provider.ts
- mcpAuth wired through CreateProviderInput / UpdateProviderInput
- providerService persistence of mcpAuth on add/update
- Path A login flow that decodes JWT exp claims and stores the
pair on the saved provider
Why keep token persistence even though the client doesn't expose
binding/grant UI any more? Per product spec the Manager will surface
余额 / 模型 / 用量 / 调用日志 (§2.3.1 in mcp-server's 待办 doc), and
the client will surface high-risk approvals (08-client-guide.md
§5). Both flows need a JWT pair we can refresh without re-prompting
for password — that machinery is already in place.
Next slice candidates per product spec (08 + 10 + 11):
- High-risk approval dialog (新增 Tier 1, mock-wired UI first)
- Task card + intent input as main client surface
- Execution feedback panel (Agnet sub-stage status)
- Delivery result panel
None of those are in this commit; this commit is purely cleanup.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
What ships:
Bindings tab:
- Create binding modal (type / name / external_ref /
permission_scope / secret_ref / status). Permission scope is a
newline-or-comma textarea that splits into a string[].
- Soft-delete (status = revoked) with red confirm dialog.
Grants tab:
- List with binding name + role + allowed actions + scope + status
+ expires.
- Create grant modal: pick binding → checkbox-select allowed
actions from THAT binding's permission_scope (auto-cleared when
binding changes), set binding_scope / role / expires_at.
- Revoke with red confirm dialog.
Shared:
- Tab switcher with active-state underline + count badge.
- Refresh button per tab (independent fetch state).
- Error banners with dismiss; mutation errors surface in modals.
- i18n: ~30 new keys per locale (zh + en).
Backend (no change):
Slice 3's /api/heicode-resources/* proxy already handles POST /
PUT / DELETE because it forwards verb + body verbatim.
The Authorization-header refresh logic (60s buffer) automatically
keeps mutations working across the 24h JWT boundary.
mcp-server safety nets the user can rely on (already enforced):
- 422 RESOURCE_GRANT_SECRET_REJECTED if metadata/constraints/scope
contains plaintext credential keys
- 400 RESOURCE_GRANT_INVALID if grant.allowed_actions ⊄ binding.scope
- 403 FORBIDDEN_SCOPE on cross-user binding/grant access
Cosmetic notes:
- GrantFormModal hooks were reordered to satisfy React's "hooks
before any early return" rule.
- useEffect that prunes allowed_actions when the picked binding
changes uses an internal `changed` flag to avoid a setState loop.
Slice 5 candidates (not in this commit):
- Edit binding (PUT)
- metadata + constraints power-user JSON editor
- Grant suspend/unsuspend
- inline filtering (type / status)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Backend:
1. Extend SavedProvider schema with optional mcpAuth field
(accessToken / refreshToken / accessExpiresAt / refreshExpiresAt /
managerLoginUrl / userId / channelId). Wired through
CreateProviderInput and UpdateProviderInput so providerService
persists tokens to providers.json.
2. handleLoginWithCredentials (Path A) now decodes the JWT exp claim
of both tokens (no signature verification — issuer just authed us)
and stores the resulting mcpAuth object on the saved provider.
Documented TTL (24h access / 7d refresh) used as fallback if exp
claim missing.
3. New handler api/heicode-resources.ts — proxy for the local server
route /api/heicode-resources/{*path}. It:
- Reads mcpAuth from the active provider (401 if missing)
- Refreshes the access token if < 60s from expiry by calling
<managerLoginUrl>/api/auth/refresh; persists the new pair
back to providers.json before forwarding
- Returns 401 if refresh token is also expired (re-login needed)
- Forwards request to <managerLoginUrl>/api/resources or
/api/resource-grants with Authorization: Bearer <accessToken>
- Passes status + body through
4. router.ts: register case 'heicode-resources'.
5. errorHandler: add ApiError.unauthorized(401) and badGateway(502)
factories used by the proxy.
Desktop:
6. New api/heicodeResources.ts client + types (ResourceBinding,
ResourceGrant, etc. mirroring mcp-server contract). Slice 3 only
exposes listBindings + getBinding.
7. New stores/resourceStore.ts (zustand) with bindings, isLoading,
hasFetched, error + fetchBindings action.
8. pages/ResourceBindings.tsx upgraded from shell to a real list:
- Auto-fetches on mount
- Shows loading skeleton, error banner with dismiss, empty state,
or a 5-column table (Name / Type / External ref / Status /
Permission scope)
- Refresh button in the header
- Footer note about CRUD coming in slice 4
9. i18n: 14 new keys (common.dismiss + resources.refresh / refreshing
/ col.* / error.title / footer.cruComingSoon) in both zh + en.
E2E behaviour after install: log in via 55@55.com / By@123456., open
Resources tab — local server proxies to apimtaiji and lists whatever
ResourceBindings the user has on mcp-server. New test account 55@55.com
has 0 bindings, so empty state shows up.
Slice 4 next: Create / Edit / Delete binding modals + Grants UI.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Per docs/vision-heicode-full-stack-agentic-dev.md and plan.md P1, Heicode
is a full-stack agentic dev platform — not a chat-only client. cc-haha
needs a "Resources" entry where users bind Git repos / SK / project docs
/ cloud accounts and grant them to sub-agents.
mcp-server team has the P1 9 endpoints live (POST/GET/PUT/DELETE
/api/resources, POST/GET/DELETE /api/resource-grants — see
Heicode-接口契约文档.md §2-§3). cc-haha has not consumed them yet.
This commit ships slice 2 (page shell):
- tabStore: new TabType 'resources' + RESOURCES_TAB_ID export
- Sidebar: new nav entry between 'scheduled' and 'terminal' (link icon)
- ContentRouter: route 'resources' tab to <ResourceBindings />
- pages/ResourceBindings.tsx: header + "coming soon" placeholder card
- i18n: sidebar.resources + resources.* keys (zh + en)
Slice 3 (next): persist mcp-server JWT in provider record so the local
Bun server can proxy /api/heicode-resources/* to apimtaiji with a fresh
Authorization Bearer header. Refresh logic on the 24h boundary.
Slice 4 (next): actual Bindings list + Create/Delete + Grants list +
Create/Revoke modals.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>