Dziennik odstępstw tego prywatnego forka od upstreamu wavetermdev/waveterm.
Cel: każda zmiana w izolowanych plikach, minimalne dotknięcia kodu upstreamu →
łatwy rebase. Każdy punkt: co, gdzie, dlaczego.
Zestaw zmian w kokpicie agentów zgłoszony po pracy na żywo.
Cel: móc rozgałęzić (fork) sesję do NOWEGO terminala oraz odróżnić brancze w historii. Fork
Claude Code = claude --resume <parent> --fork-session --session-id <new> (flaga udokumentowana;
zweryfikowane empirycznie, że --session-id jest respektowane razem z --fork-session → nowa sesja
<new>.jsonl z polem forkedFrom, rodzic nietknięty). W pełni config-driven (żadnej listy
agentów w kodzie): zdolność forka to launch:forkargs w agents.json — ma ją tylko Claude.
Detekcja branchy (nowe pole forkedfrom w podsumowaniu sesji):
pkg/agentsession/agentsession_list.go—summarizeTranscript→scanTranscriptHeadczyta w JEDNYM przejściu głowy transkryptu i snippet, iforkedFrom.sessionId(parserclaudeForkedFrom, tylko dla Claude). Nowe poleSessionSummary.ForkedFrom.pkg/wshrpc/wshrpctypes.go—AgentSessionSummary.ForkedFrom;AgentDef.ForkArgs.pkg/wconfig/settingsconfig.go—AgentConfigType.ForkArgs(launch:forkargs).pkg/wshrpc/wshserver/wshserver.go— przekazanieForkArgs(build+toRpc) iForkedFromw konwersji.pkg/wconfig/defaultconfig/agents.json—"launch:forkargs": ["--fork-session"]dlaagent@claude.
Wykonanie forka (jednorazowy marker meta):
pkg/agentsession/agentsession.go—AgentDef.ForkArgs,CanFork()(wymaga forkargs + createargs dla deterministycznego id),ResolveFork()(kolejność: autonomy + resume(parent) + fork + create(new)).pkg/waveobj/wtypemeta.go— metaagent:forkfrom(parent id, jednorazowy).cmd/wsh/cmd/wshcmd-agentresume.go— gdyagent:forkfromustawione i brakagent:sessionidi agentCanFork():ResolveFork, zapis nowego id na blok, wyczyszczenieagent:forkfrom(restart wznawia fork normalnie, bez ponownego forkowania). Banner „⎇ Branching…".
Frontend:
frontend/app/agent/agentsessionsmodel.ts—agentCanFork()(czytalaunch:forkargs),forkSessionIntoBlock()(nowy blok zagent:forkfrom) — współdzielone.frontend/app/agent/agentsessionspanel.tsx— badge „branch" + ikona przy tytule dla sesji zforkedfrom; ikonowy przycisk Branch (obok Resume) tylko dla agentówagentCanFork.frontend/app/agent/agentsessions.scss— style.ap-branch/.ap-branchmark/.ap-branchdot/.ap-row--branch.frontend/app/view/term/term-model.ts(upstream, minimalnie) — pozycja context-menu „Branch This Session" dla bloku z uruchomionym, forkowalnym agentem.
frontend/app/agent/agentsessions.scss — tytuł 12.5→11px, meta 9.5→9px, ciaśniejsze paddingi/gap i
kompaktowy przycisk Resume, żeby zmieściło się więcej sesji i więcej tekstu na wiersz (łatwiejsza
identyfikacja sesji do wznowienia).
frontend/app/view/term/termlinks.ts — isPathLike odrzuca teraz tokeny-ułamki/ratio/daty ze slashem,
gdzie KAŻDY segment to liczba z opcjonalną krótką jednostką (NumericRatioRegex): 0k/1000k, 5/10,
50%/100%, 2026/07/03. Wymóg wiodącej cyfry w każdym segmencie chroni realne ścieżki względne
(pkg/agentsession). Czysto frontendowe.
Cel: okno pokazywane przy PIERWSZYM uruchomieniu (NewInstallOnboardingModal, bramka
!clientData.tosagreed) to teraz brandowany ekran forka — nie „Welcome to Wave Terminal".
Design: greeting/MOTD terminala (sygnatura), spójny z redesignem (mint, ostre krawędzie,
mono). Atrybucja Wave w wierszu „built on" spełnia wymóg NOTICE/Apache-2.0.
Nowe (izolowane) pliki:
frontend/app/onboarding/terminalcloudgreeting.tsx+terminalcloudgreeting.scss— komponentTerminalCloudGreeting. Titlebar (znak>_+terminal-cloud+ status● first run), prompt~ $ welcomez żywym mint kursorem, mono headline „Welcome to Terminal Cloud", tagline, oraz „status readout" kredytów (developed by/sponsored by/built on/contact) i mintContinue. Loga = jedyne plamy koloru.frontend/app/onboarding/statxplorer-logo.png— oryginalne logo StatXplorer (z ich strony, 260×144, czarny wordmark + zielona kropka#0dd14a), na jasnym chipie.- Logo CloudBots = inline 3 heksagony (
#A9F5D6/#84DCF1/#84F1F1) + wordmark „CLOUDBOTS" w prawdziwym foncie brandu Exo 2 (zC:\Projekty\Strona Firmowa\svgs\cloudbots-logo-*.svg).
Font: frontend/util/fontutil.ts — dobundlowany Exo 2 600 (latin),
public/fonts/exo2-latin-600.woff2 (Google Fonts). Mono = JetBrains Mono (już w bundlu).
Dotknięty upstream (minimalnie):
frontend/app/onboarding/onboarding.tsx—InitPageodchudzony do<TerminalCloudGreeting>;Continue=AgreeTos()(raz) + zamknięcie modala (retired: telemetry/GitHub-star/features flow). Modal dla stronyinitrenderuje sam panel (!p-0 !rounded-none !border-0 !bg-transparent, bezOnboardingGradientBg). Usunięty auto-advance do „features" (żeby preview pokazywał greeting). Nowy eksportshowOnboardingGreeting()(ustawiapageNameAtom="init"+modalsModel.newInstallOnboardingOpen=true) — czysto frontendowy re-show, bez ruszania flagi.cloudbotsbranding.tsx— nieużywany (zastąpiony przez greeting); zostaje w repo.
Podgląd (sposób resetu, wybór usera): pozycja „Welcome Screen" w menu koła zębatego
(widgets.tsx SettingsFloatingWindow) woła showOnboardingGreeting() — re-show onboardingu
nawet po zaakceptowaniu ToS, bez dotykania tosagreed (frontend, repeatowalne).
Wszystko frontendowe → działa po Ctrl+Shift+R (bez restartu backendu).
Pkt 8 — UX agentów/folderów: resolucja cwd, uruchamianie w istniejącym terminalu, autonomia, toggle widgetów
Zestaw poprawek zgłoszonych po pierwszym uruchomieniu dev (foldery + zachowanie agentów/widgetów).
Problem: widget „git"/Source Control otwierał się w katalogu procesu wavesrv (C:/Projekty/new Wave),
a nie w folderze terminala, w którym pracuje użytkownik — bo resolucja brała tylko getFocusedBlockId(),
które przy fokusie na bloku nie-terminalowym (np. panelu Source Control) zwracało zły/pusty cwd.
Rozwiązanie: resolucja idzie teraz po historii fokusu do najświehigher terminala.
frontend/layout/lib/layoutModel.ts(upstream, minimalnie) — nowy publicznygetFocusedBlockIdStack()mapujący prywatnyfocusedNodeIdStack(MRU) na blockIds. Klik w pasek widgetów nie rusza tego stosu.frontend/app/store/global.ts— nowygetFocusedBlockIdStack()(wrapper) +resolveDefaultTermCwdprzepięte na przejście stosu (pomija bloki nie-terminalowe).frontend/app/agent/agentsessionsmodel.ts—resolveAgentCwd()analogicznie przez stos fokusu.
Problem: start agenta z przycisku agenta w nagłówku terminala zawsze tworzył drugi terminal.
Rozwiązanie: gdy panel jest scope'owany do bloku (ctx.blockId) i blok to wolny lokalny shell bez
działającego agenta, uzbrajamy meta bloku i wpisujemy wsh agentresume do istniejącej powłoki
(ta sama ścieżka resolucji co initscript).
frontend/app/agent/agentsessionsmodel.ts—canLaunchAgentInBlock(blockId)(term + shell + brak connection + brakactiveAgentAtom).frontend/app/agent/agentsessionspanel.tsx—runAgentInBlock()(SetMeta +sendDataToController),launchNew/launchResumeprzyjmujątargetBlockId;blockIdwątkowany przez AgentView/SessionRow.
Cel: wybór poziomu uprawnień per agent (np. „Skip permissions" = --dangerously-skip-permissions),
z możliwością ustawienia domyślnego. W pełni w agents.json (poziomy + flagi + domyślny).
pkg/wconfig/defaultconfig/agents.json—autonomy:levels({key,label,args}) +autonomy:defaultdla claude/codex/cursor (opencode/antigravity: tylko „default" — flagi skip do potwierdzenia).pkg/wconfig/settingsconfig.go—AgentConfigType.AutonomyLevels/AutonomyDefault+ typAgentAutonomyLevel.pkg/wshrpc/wshrpctypes.go—AgentDef.AutonomyLevels/AutonomyDefault+ typAutonomyLevel.pkg/agentsession/agentsession.go—AgentDef.AutonomyLevels/AutonomyDefault,autonomyArgs(),ResolveLaunchWithAutonomy()(flagi autonomii prependowane przed argsy agenta);ResolveLaunchzachowane.pkg/wshrpc/wshserver/wshserver.go— mapowanie autonomii wbuildAgentDef/toRpcAgentDef(+ helpery).cmd/wsh/cmd/wshcmd-agentresume.go— flaga--autonomy, odczytagent:autonomyz meta,ResolveLaunchWithAutonomy,agentDefFromRpcmapuje poziomy.pkg/waveobj/wtypemeta.go— metaagent:autonomy(→task generate).frontend/app/agent/agentsessionsmodel.ts—agentAutonomyLevels()/agentDefaultAutonomy()(z configu).frontend/app/agent/agentsessionspanel.tsx+agentsessions.scss— dropdown „Autonomy" (widoczny gdy >1 poziom), wybór zapamiętany wlocalStorageper agent = domyślny; wpinany w launch (metaagent:autonomy).
frontend/app/workspace/widgets.tsx— mapawidgetOpenBlocks(per tab+widget) +widgetToggleKey;handleWidgetSelect(widget, env, newWindow): powtórny klik zamyka blok (layoutModel.closeNode),Ctrl/Cmd+Clickzawsze otwiera nowy. Reszta logiki cwd (files/gitscm) bez zmian.
frontend/app/block/block.scss— pod.block-frame-end-icons:gap: 2px+align-items: center(był brak marginesu), przyciski.wave-iconbutton:not(.wave-agent-headerbutton)= jednolite 22×22, wycentrowane,border-radius: 0, hover mint (--accent-color) z wygaszeniem dla.disabled/.no-action; ikona magnify (SVG) też mint na hover. Pill agenta (.wave-agent-headerbutton) i globalny.iconbuttonnietknięte.
frontend/app/workspace/widgets.tsx— tylko klasy:py-1.5→py-1, usunięterounded-sm(ostre krawędzie), ikonytext-base→text-sm/ supercompacttext-sm→text-xs, kontenerw-10→w-9, badge DEV18px→15px. Mirror pomiarowy (measurementRef) zmieniony 1:1, więc tryby normal/compact/supercompact nadal działają. Logika toggle (T6) nietknięta.
Natywne menu (Electron Menu.buildFromTemplate) są nieostylowywalne CSS-em → zastąpione własnym
komponentem React, żeby pasowały do forka (Inter, ostre krawędzie, jaśniejsza powierzchnia, mint hover).
frontend/app/element/wavecontextmenu.tsx(NOWY) —WaveContextMenu+showWaveContextMenu()(baza zpreview/preview-contextmenu.tsx): floating-ui, submenu, separatory, checkbox/radio, disabled, sublabel, dismiss (klik poza / Esc / blur / resize / scroll). Role (copy/cut/paste/selectAll/ undo/redo/delete) reimplementowane wperformRole(execCommand + clipboard API dla paste), a panele robiąonMouseDown → preventDefault, więc pod menu zostaje fokus/zaznaczenie i role działają.frontend/app/element/wavecontextmenu.scss(NOWY) — styl (--ap-*: Inter, #1e2223, mint), max-height+scroll.frontend/app/store/contextmenu.ts—showContextMenuprzekierowany nashowWaveContextMenu(opts onSelect/onCancel/onClose zachowane). Stary elektronowy path (_convertAndRegisterMenu) zostawiony martwy dla łatwego revertu. Wszystkie menu idą przez jedno wejście → 100% pokrycia (0 wywołań natywnych).frontend/app/app.tsx— montaż<WaveContextMenu />wAppInner.
frontend/app/view/term/term-model.ts(getSettingsMenuItems) — po Split Horizontally/Vertically dwa submenu „Split Horizontally/Vertically with Agent" listujące agentów (zconfiguredAgents). Wybór dzieli blok i startuje agenta w nowym panelu (dziedziczy folderresolveTermWorkingDir+ zapamiętaną autonomięgetStoredAutonomy, launch przezwsh agentresume).frontend/app/agent/agentsessionsmodel.ts—getStoredAutonomy/setStoredAutonomyprzeniesione tu (z panelu) jako współdzielone; panel importuje je z modelu.
Flagi sprawdzone empirycznie (claude/codex/cursor/opencode zainstalowane) + docs (antigravity):
- Claude: poziomy przez
--permission-mode {plan|acceptEdits}+--dangerously-skip-permissions. - Codex: POPRAWKA — usunięty nieistniejący
--full-auto; „auto" =--ask-for-approval never --sandbox workspace-write, „full" =--dangerously-bypass-approvals-and-sandbox. - Cursor:
--mode plan+--force. - OpenCode: dodany
--auto(auto-approve). - Antigravity: dodany
--dangerously-skip-permissions(docs — CLI toagy, nieantigravity;detect:patternrozszerzony oagy;cmd:binzostawiony — do potwierdzenia po instalacji).
canLaunchAgentInBlockutwardzone (frontend/app/agent/agentsessionsmodel.ts): odrzuca też bloki z ustawionymagent:type(terminal agenta), niezmontowany model, oraz gdy shell integration ≠ „ready". Naprawia bug: klik „New" wstrzykiwałwsh agentresumedo terminala z DZIAŁAJĄCYM agentem (Codex).- Menu kontekstowe — submenu portalowane (
frontend/app/element/wavecontextmenu.tsx): każdy panel (root + submenu) w osobnymFloatingPortal.overflow-y:autona panelu ztransform(floating-ui) przycinał submenuposition:fixed→ „Split with Agent" (selektor agenta) było niewidoczne. Dismiss przezclosest('.wave-contextmenu')(obejmuje portalowane submenu). - Pasek widgetów icon-only (
frontend/app/workspace/widgets.tsx): usunięte etykiety (koniec ucinania „ter…"/„sysi…"), pełne nazwy w tooltipach; mirror pomiarowy zsynchronizowany 1:1; toggle (T6) nietknięty. - Własny dropdown (
frontend/app/element/wavedropdown.tsx+.scss, NOWE): zastępuje natywne<select>w panelu agenta (selektor agenta + autonomii) — mint highlight zamiast systemowego niebieskiego, Inter, ostre krawędzie, size-middleware = szerokość triggera. Portal jako React-descendant opener'a → panel się nie zamyka przy wyborze opcji. Usunięte martwe.ap-select*wagentsessions.scss. - Mockup designu:
C:/Projekty/new Wave/design-reference.html(poza repo) — pełny wizualny spec forka (mint, ostre krawędzie, Inter) jako referencja do ujednolicania UI.
- HIGH
canLaunchAgentInBlock: reuse tylko gdyshellIntegrationStatusAtom === "ready"(brak integracji / status nieznany → nowy blok; bez integracjiactiveAgentAtomteż bywa pusty). - HIGH menu z
termlinks(stub-event bez współrzędnych) trafiało na (0,0) →contextmenu.tsśledzi ostatnią pozycję kursora (pointermove) i używa jej jako fallback (ev.clientX ?? lastPointer.x). - MED role-only items (
{role:"copy"}w AI panelu) renderowały pusty wiersz →itemLabel()humanizuje rolę (Copy/Paste/…) dla etykiety idata-context-menu-item. - MED scroll wewnątrz długiego menu zamykał je → scroll-listener ignoruje target w
.wave-contextmenu. - MED
contextmenu.test.tsprzepisany pod nową ścieżkę (mockwavecontextmenu, cykl onClose) — 3/3 zielone. - LOW prawy-klik wewnątrz menu nie propaguje do app-level
onContextMenu(nie otwiera drugiego menu). - Odłożone (LOW): pełna nawigacja klawiaturą w menu (strzałki/Enter/typeahead) — obecnie tylko Esc.
- Codex potwierdził brak aliasing-bugu w
ResolveLaunchWithAutonomy(Go).
Problem: decyzja „uruchom agenta w tym terminalu vs nowe okno" (T4/T9) opierała się na
shellIntegrationStatusAtom === "ready" (OSC-driven). Gdy integracja powłoki nie jest zainstalowana
/ status nieznany, warunek nigdy nie był spełniony → agent ZAWSZE otwierał się w nowym oknie („nie działa").
Rozwiązanie: reuse decyduje się teraz po tym, czy w powłoce bloku realnie działa proces pierwszoplanowy
(dowolny — agent, edytor, build), sprawdzany po drzewie procesów-dzieci powłoki na backendzie.
pkg/shellexec/shellpid.go(NOWY) —(*ShellProc).Pid()(pid lokalnej powłokiCmdWrap; 0 dla ssh).pkg/blockcontroller/foregroundproc.go(NOWY) —GetBlockForegroundRunning(blockId):getController→*ShellController→GetShellPid()→gopsutil/process.Children(); ignoruje helpery ConPTY (conhost.exe/OpenConsole.exe). Zwraca(running, childName).pkg/wshrpc/wshrpctypes.go+wshserver/wshserver.go— nowy RPCBlockForegroundStatusCommand(blockId)→BlockForegroundStatusData{running, childname}(+task generate).frontend/app/agent/agentsessionsmodel.ts—canLaunchAgentInBlockasync: bramka strukturalna (isReusableShellBlock: term + shell + brak connection) + zapytanie do RPC; usunięta zależność odactiveAgentAtom/shellIntegrationStatusAtom. Świadomie NIE odrzucamy bloku z ustawionymagent:type— terminal, w którym agent padł/wyszedł (np. antigravity „executable not found"), stoi bezczynnie na prompcie i ma być reużywalny; o zajętości decyduje wyłącznie realny proces.frontend/app/agent/agentsessionspanel.tsx—launchNew/launchResumeasync (await canLaunch…), wywołania owinięte wfireAndForget.
frontend/app/workspace/widgets.tsx— każdy przycisk widgetu ma subtelne tło (bg-white/[0.05]) + odstęp (mb-0.5) → segmentowany, wyróżniony wygląd (spójny z radius-0).AgentWidgettoggle: ponowny klik zamyka panel (viaisOpenAtom), aktywny stan podświetlony (bg-hoverbg).
Problem: przy reuse injekcja wysyłała \n → PowerShell utykał na >> (PSReadLine: \n=Ctrl+J=nowa
linia, \r=Enter). Dodatkowo widoczne było surowe wsh agentresume, a nowy blok agenta nie pokazywał
żadnej informacji.
frontend/app/agent/agentsessionspanel.tsx(runAgentInBlock) +frontend/app/view/term/term-model.ts(„Resume agent here"): submit przez\r(nie\n) + flaga--clearcmd.cmd/wsh/cmd/wshcmd-agentresume.go— zawsze baner z etykietą agenta: „▶ Launching …" (świeży) / „⟳ Resuming …" (wznowienie), zdef.Labelzamiast surowego typu. Odpala się w OBU ścieżkach (nowy blok przez initscript + reuse). Ukryta flaga--clearcmd: czyści echo wpisanej komendy (\x1b[1A\x1b[2K\r) przy injekcji w interaktywny prompt (zakłada 1-liniowy prompt).
Problem: zamknięcie ostatniego bloku → DeleteBlock kasuje pusty tab → DeleteTab przy ostatnim
tabie wołał CloseWindow → window-all-closed → quit aplikacji.
pkg/wcore/workspace.go(DeleteTab) — gdy nie zostaje żaden tab, zamiastCloseWindowtworzy świeży tab (CreateTab) i zwraca jego id.WorkspaceService.CloseTabwidzinewActiveTabId != ""→CloseWindow=false, electron przełącza na nowy tab. Pokrywa obie ścieżki (zamknięcie bloku i taba); okno zostaje otwarte, aplikacja nie znika.- Aktualizacja (runda 2 / T20): zastępczy tab seeduje teraz launcher (ekran startowy / widget
picker), a nie auto-terminal.
CreateTab(..., isInitialLaunch=true)pomija domyślny layout terminala, po czymApplyPortableLayout(newTabId, GetEmptyTabLayout(), true)wstawia blokview:"launcher". NowyGetEmptyTabLayout()wpkg/wcore/layout.go. Odpowiedź na „czy nie da się aby nie było terminala" — po zamknięciu wszystkiego zostaje ekran startowy, nie kolejna powłoka. - Hardening kolejności (fix z review 2026-07): zastępczy tab powstaje przed utrwaleniem usunięcia
ostatniego taba.
CreateTabre-czyta i re-zapisuje workspace, więc po jego sukcesieDeleteTabprzeładowujewsz DB i wycina stary tab; gdyCreateTabpadnie, zwraca błąd z workspace nadal wskazującym stary (już bezblokowy) tab — zamiast utrwalonego stanu zero-tab (okno bez niczego). WcześniejDBUpdate(ws)z pustymiTabIds/ActiveTabId=""szedł przedCreateTab.
pkg/wconfig/defaultconfig/settings.json—web:defaulturlz repo Wave →https://statxplorer.com(spójne ze sponsorem w stopce launchera). Default embedowany (go:embed), scalany pod override użytkownika → wymaga rebuildu wavesrv, nie nadpisuje konfiguracji usera.
pkg/wconfig/defaultconfig/termthemes.json— motywdefault-dark:"cursor": ""→"#00ced1"(mint = akcent forka). Wszystkie terminale na default-dark mają teraz mint cursor. Styl kursora (block/bar/underline) bez zmian — to tylko kolor.
frontend/app/block/block.scss—.wave-agent-headerbuttonborder-radius: 5px→0.frontend/app/agent/agentheaderbutton.tsx— monogram fallbackurounded-[4px]→rounded-none. (Logo brandów Claude/Codex/Cursor to znaki firmowe — bez zmian.)
frontend/app/element/wavecontextmenu.scss—min-width 190→168,font-size 12→11.5, itemmin-height 27→23+ ciaśniejszy padding, separatorymargin 4→3. Menu (Image #9/#14) mniejsze.
frontend/types/custom.d.ts—ContextMenuItem+ opcjonalneicon?: React.ReactNode(renderuje tylko in-app WaveContextMenu; natywna ścieżka electrona je ignoruje).frontend/app/element/wavecontextmenu.tsx+.scss— slot.wave-contextmenu-icon(dziedziczy kolor hover, opacity 0.55).frontend/app/view/term/term-model.ts— helpersplitPreviewIcon(dir)rysuje 2-komórkowy diagram (obok siebie = horizontal, jeden nad drugim = vertical) i podpina go do „Split Horizontally/Vertically" oraz obu „…with Agent".
frontend/app/tab/tabbar.tsx— przycisk App Menu (Windows,showWorkspaceAppMenu) zfa-ellipsis(26px, niejasne) →Tooltip content="Menu"+fa-bars(18px).divRef/divStyle/divOnClickzachowują pomiar szerokości,WebkitAppRegion:no-dragi akcję.
frontend/app/agent/agentsessionspanel.tsx— render kapowany doAgentPageSize=20;search/sortnadal po całym zbiorze (wyszukiwanie widzi wszystkie sesje), przycisk „Show N more · X/Y" dokłada kolejne 20;showCountresetuje się przy zmianie query/agenta/trybu.frontend/app/agent/agentsessions.scss— stany.ap-loading/.ap-emptymająmin-height:180px(loading nie zwija popovera → floating-ui nie przerzuca placementu → kontrolki nad listą nie skaczą); styl.ap-more.
- Ikony splitu (poprawka T18): teraz po prawej od tekstu (
.wave-contextmenu-iconmargin-left:auto, render po labels wwavecontextmenu.tsx) i tylko kontur —splitPreviewIconrysuje ramkę + jedną linię podziału (bez wypełnienia / „kropkowanego" tła). - Agent na górze paska widgetów:
frontend/app/workspace/widgets.tsx—<AgentWidget>przeniesiony przedwidgets.mapw obu trybach (normal + supercompact). Wcześniej był na dole listy. - Ikona workspace:
frontend/app/tab/workspaceswitcher.tsx— domyślna (niezapisany workspace) zWorkspaceSVG(2×2 kratka) →fa-sharp fa-solid fa-layer-group; usunięty nieużywany import SVG. - Hamburger wyżej:
tabbar.tsx— gliffa-barszrelative top-[-2px](był odrobinę za nisko). - Mniejsze przyciski headera bloku:
block.scss.block-frame-end-icons .wave-iconbutton22→20pxfont-size:13px;gap2→4px (oddech).
- Spójność czcionek:
agentsessions.scss--ap-monoJetBrains Mono → Hack (jedyny mono w forku = Hack, jak reszta appki). Główne źródło „różnych czcionek". - Lżejsza typografia nagłówków (Codex #4):
theme.scss--header-font700 11px→600 11px. - Restraint akcentu na badge agenta (Codex #1/#8):
block.scss.wave-agent-brandedtło0.14→0.10, border0.4→0.35, hover0.24→0.20. - Wyrównanie hamburgera z ikoną workspace:
.tab-bar-wrappermaalign-items:end+ workspace mamargin-bottom:4px, więc hamburger (bez struktury wysokości) siedział niżej.tabbar.tsx— kontener hamburgera dostałh-[22px] mb-1+ glif16px(bez ad-hoc nudge) → identyczny środek pionowy jak workspace. - Wskaźniki aktywności (Codex #6/#7, wybór usera):
tab.scssaktywny tab = mint górna linia (box-shadow: inset 0 2px 0 var(--accent-color), bez przesunięcia layoutu);widgets.tsxotwarty widget Agent = mint lewa linia (shadow-[inset_2px_0_0_0_#00ced1]). (Zwykłe widgety to launchery jednorazowe — nie mają trwałego stanu „aktywny", więc wskaźnik tylko dla taba i otwartego panelu Agent.) - Normalizacja ikon (Codex #2, wybór usera): tab-bar = 16px (hamburger +
.workspace-icon15→16px), pasek widgetów =text-[15px](byłotext-sm/14px), header bloku = 13px/20px (z rundy T22 wyżej).
Konsultacja UI z Codexem (gpt-5.5, xhigh): pełna lista 10 rekomendacji zapisana; wdrożono bezpieczny podzbiór (typografia, sizing/gap headera, restraint akcentu, spójność mono). Większe zmiany motywu (globalne tiery kontrastu tła/tekstu, restyl aktywnego taba, wskaźnik zaznaczenia na pasku widgetów, normalizacja rozmiarów ikon tab-bar/widget-bar) czekają na decyzję użytkownika (mogą regresować istniejący wygląd) — patrz pytanie opcji.
- Menu Settings/Tips/Secrets/Release Notes/Help (
SettingsFloatingWindowwwidgets.tsx) dopasowane do ostrego designu: kontenerrounded-lg→**rounded-none,p-2→p-1+min-w-[168px]; itemygap-3→gap-2.5,px-3 py-2→px-2.5 py-1.5,rounded→rounded-none; labeltext-sm→text-[12px]**, ikonatext-base→text-[13px]i mint na hover (group-hover:text-accent). Spójne z restylowanymWaveContextMenu. - Selektor agenta mniejszy (Image #20):
wavedropdown.scss— nowy wariant.wave-dropdown--sm .wave-dropdown-trigger(padding:5px 9px,font-size:11.5px).agentsessionspanel.tsx— dropdown agenta dostał propsm(Autonomy już go miał → oba triggery teraz kompaktowe).agentsessions.scss.ap-btn(przycisk „New")height32→28px,font-size12→11.5,padding0 13px→0 12px— wyrównany do niższego selektora. (.ap-pane-actions .ap-btn= 34px zostaje — to osobny obszar akcji w bloku, nie rząd selektora.)
- Ikona folderu w nagłówku (złoto → mint):
frontend/app/view/term/term-model.ts— przycisk „Working folder" wviewTextmiałiconColor: var(--warning-color)(krzykliwe złoto) →var(--accent-color)(mint, spójne z akcentem forka; usuwa off-brandowy kolor). - Ikona lokalnej maszyny (laptop) cieńsza:
frontend/app/block/connectionbutton.tsx—makeIconClass("laptop", false)(solid) →makeIconClass("regular@laptop", false)(fa-sharp fa-regular, lżejszy outline; kolor bez zmian =--color-secondary). Wariant regular/sharp jest już używany w forku (np.regular@thumbs-up), więc font jest dostępny. - Mniejsza czcionka tekstu nagłówka (11 → 10px):
frontend/app/block/block.scss—.block-frame-textfont-size11→10px (ścieżka robocza +cmdTexttypu „Claude Code v2.1");.connection-buttondostałfont-size: 10px(nazwa hosta „krzys@DESKTOP…"). Terminal = 12px (--fixed-font), więc nagłówek jest teraz wyraźnie mniejszy (był praktycznie równy). Zmiana dotyczy nagłówków wszystkich bloków (współdzielony komponent) — świadomie, spójnie. - Kompaktowy modal „Working folder" (
variant="folder"):frontend/app/agent/agentsessionspanel.tsx— etykieta przycisku „Set from terminal window" → „From terminal" (oba przyciski luźno w jednej linii).agentsessions.scss—.ap-panepadding12px 13px 13px→**9px 12px 11px;.ap-pathcardpadding10px 11px→7px 10px,margin-bottom12→8px**; heksagon.ap-pathcard-ic26→22px (glif 11→10px);.ap-pane-actions .ap-btnheight34→28px (zastępuje notkę z T23, że 34px zostaje).
- Ikony menu ustawień cieńsze/sharp (Image #21):
widgets.tsxSettingsFloatingWindow— glify solid →regular@…(fa-sharp fa-regular, lżejszy kontur): Settingsregular@gear, Tipsregular@lightbulb, Secretslock→**regular@key, Release Notesbook-open→regular@rocket**, Helpregular@circle-question. - Nowa pozycja „Welcome Screen" (
regular@terminal) w tym menu — wołashowOnboardingGreeting()(pkt 9): re-show onboardingu do podglądu, bez ruszaniatosagreed. - Agents: usunięta zakładka „Raw JSON" (Image #22):
waveconfig-model.ts— wpisagents.jsonhasJsonView: true→**false** (jak Secrets). Pasek zakładek Visual/Raw JSON renderuje się tylko przyhasJsonView, więc znika tylko dla agentów; inne configi bez zmian. Nieaktualne zdanie o „Raw JSON tab" wagentsconfig.tsxusunięte. - Agents: ostre krawędzie boxów (Image #22):
agentsconfig.tsx— karty agentów, formularz edycji, box „How detection works", box ostrzeżenia built-in, box „No custom agents", inputy i przyciski akcji: usunięterounded-lg/rounded-md/rounded→ radius 0 (spójne z redesignem). Pille/kropki (rounded-full) zostają.
Definicja agent@antigravity w pkg/wconfig/defaultconfig/agents.json powstała „na docs" (T5, cmd:bin
do potwierdzenia po instalacji). Po instalacji zweryfikowano realia CLI: agy 1.0.16
(%LOCALAPPDATA%\agy\bin\agy.exe, jest w trwałym user-PATH → exec.LookPath OK), backend =
Antigravity.exe/language_server.exe w trybie CLI (product=antigravity), dane w
~/.gemini/antigravity-cli/ (współdzieli dom ~/.gemini z osobnym Gemini CLI — to NIE ten sam agent).
Poprawki z agy --help:
cmd:bin"antigravity"→"agy"(realna komenda;antigravitynie jest binarką).detect:pattern→^(agy|antigravity)\b(agy pierwsze — to faktyczny launcher).launch:resumeargs[]→["--conversation", "{id}"](agy resumuje konwersację po ID; mapuje się 1:1 na{id}forka — inaczej niż Gemini CLI, który resumuje po indeksie).-c/--continue= ostatnia.store:roots[]→["~/.gemini/antigravity-cli/conversations"](sesje CLI:conversations/<UUID>; sesje IDE-Cascade są osobno w~/.gemini/antigravity/conversations).store:driverzostaje""(parser proto+SQLite „trajectory store" niepisany — resume-by-ID z panelu wymaga przyszłego drivera „antigravity").autonomy:--dangerously-skip-permissionspotwierdzony (był poprawny); dodany poziomsandbox=--sandbox. Domyślnyrequest-reviewzgadza się ztoolPermission=request-revieww logu CLI.
Nie rusza Go — plik konfiguracyjny (bez task generate). Konfig referencyjny w pamięci agenta
(antigravity-gemini-cli-config.md).
Greeting pierwszego uruchomienia pokazuje się tylko przy tosagreed=false; po akceptacji znikał, a jedyny
re-show był ukryty w menu ••• („Welcome Screen", T25). Na prośbę usera dodany widoczny przycisk w prawym
górnym rogu.
frontend/app/tab/tabbar.tsx— wrightContainerRef(klaster prawego rogu, obokUpdateStatusBanneri przycisków okna) nowy<button>„About Terminal Cloud" (ikonafa-sharp fa-regular fa-circle-info, hover na mint),onClick→showOnboardingGreeting()(ten sam seam co pozycja menu).WebkitAppRegion: no-dragżeby był klikalny mimo drag-regionu paska. ImportshowOnboardingGreetingz@/app/onboarding/onboarding.- Szerokość przycisku wchodzi automatycznie do
rightContainerWidthwsetSizeAndPosition(pomiargetBoundingClientRect), więc obliczenia szerokości zakładek same to uwzględniają — bez dodatkowego refa.
T28 — Powiadomienia agentów: który agent + która zakładka, mintowa kropka, własny dźwięk (feedback z testu na żywo)
Test na żywo (Claude, Windows) ujawnił trzy braki: powiadomienie miało tytuł „Terminal agent" (bez nazwy agenta), nie mówiło z której zakładki, na zakładce nie było żadnego wskaźnika, a dźwięk był taki sam jak zwykłego powiadomienia. Do tego user chciał mintową kropkę zamiast żółtego dzwonka.
frontend/app/view/term/agentnotify.ts(przebudowa mostu):- Który agent (tytuł). Stary
resolveAgentLabelczytał tylkoactiveAgentAtom, ustawiany wyłącznie z detekcji shell-integration (OSC 133;C) — na Windows/PowerShell i przy resume to nie strzela → fallback „Terminal agent". TerazresolveAgentTypema łańcuch fallbacków:activeAgentAtom→ trwała meta blokuagent:type(widget/wsh agentresume) → wyłuskanie z komunikatu hooka (agentLabelFromMessage: „Claude - …" → „Claude", bounded + tylko separator-, żeby nie brać zdania za etykietę). - Która zakładka (tytuł).
resolveTabNameczytaTabpotermWrap.tabId; tytuł ="<Agent> · <tab>"(np. „Claude · T1").stripAgentPrefixusuwa z treści zdublowany prefiks „ - ", więc body = samo zdarzenie („waiting for your input"). Fallbacki treści przeredagowane na samodzielne („Waiting for your input", „Needs your approval", …). - Mintowa kropka (zakładka).
notifyAgentEventwoła terazsetBadge(blockId, {icon:"circle", color:"#00ced1", priority:2})— istniejący system badge'ów agreguje kropki bloków na zakładce i sam je czyści na focus (badge nie-pidlinked). Kropka ustawiana przed throttlem powiadomienia, żeby kolejne zdarzenia utrzymywały oznaczenie.maybeNotifyOnBellużywa tego samegoresolveAgentType, więc dzwonek w bloku-agencie (bez żywej detekcji, ale z meta) też powiadamia.
- Który agent (tytuł). Stary
frontend/app/view/term/agentsound.ts— NOWY (izolowany). Odrębny chime agenta (Web Audio, bez assetu), inny per typ:waiting= pojedynczy miękki ton,approval= wznoszący (bardziej natarczywy),finished= opadająca kadencja. Gra z renderera (działa przy nieaktywnym oknie — AudioContext odblokowany wcześniejszą interakcją).playAgentChimezwracafalsegdy audio niedostępne → wtedy powiadomienie OS wraca do systemowego dźwięku; w przeciwnym razie OS-notyfikacja jestsilent:true(nasz chime zamiast „dinga").frontend/app/view/term/termwrap.ts— badge dzwonka (term:bellindicator) zmieniony z żółtego dzwonka (icon:"bell",#fbbf24) na mintową kropkę (icon:"circle",#00ced1), spójnie ze wskaźnikiem agenta.- Zero zmian Go / configu → bez
task generatei bez restartu dev (renderer, Vite HMR; istniejące terminale łapią zamianę dzwonka poCtrl+Shift+Rlub w nowym bloku).npx tsc --noEmit✅ = 0.
T37 — Pulsujący wskaźnik attention + auto-fokus na pierwszego czekającego agenta po powrocie do okna (feedback usera)
User: „kropka może pulsować i jak wrócę do okna, fokus niech przeskoczy na okno pierwszego agenta, który skończył
pracę." Rozszerzenie T28 (mintowa kropka). Czysto renderer — bez Go / task generate / restartu dev.
Pulsowanie kropki:
frontend/app/store/badge.ts— stałaAttentionBadgeColor(#00ced1) + helperisAttentionBadge(badge)(match po mintowym kolorze;Badgeto typ upstreamu, nie forkujemy jego schematu dla jednego pola). Dedup koloru:agentnotify.tsitermwrap.tsużywają tej stałej zamiast literału.frontend/tailwindsetup.css— keyframeagent-badge-pulse(oddech: opacity + scale) +@media (prefers-reduced-motion: reduce)wyłącza animację.frontend/app/block/blockframe-header.tsxifrontend/app/tab/tabbadges.tsx— klasaagent-badge-pulsena kropce gdyisAttentionBadge(nagłówek bloku i zakładka; kropka dzwonka też pulsuje — spójnie).
Auto-fokus po powrocie do okna:
frontend/app/view/term/agentattention.ts— NOWY (izolowany). KolejkawaitingBlocks(Set, kolejność wstawienia = kto pierwszy zaczął czekać; idempotentne, blok już w kolejce zachowuje starszą pozycję).markAgentAttentionwołane zagentnotify.tsposetBadge. Nasłuchwindow "focus"(lazy, dowiązany przy pierwszym marku): po powrocie do okna wybiera NAJSTARSZY wciąż czekający blok (prune po żywym stanie badge —isAttentionBadge, więc odwiedzone/wyczyszczone kropki wypadają), przełącza zakładkę jeśli blok jest na innej (getApi().setActiveTab+ krótki polling aż layout docelowej zakładki ożyje) i ustawia fokus (refocusNode), po czym zdejmuje go z kolejki (kolejny powrót → następny agent). Guarddocument.hasFocus().frontend/app/view/term/agentnotify.ts— import + wywołaniemarkAgentAttention; dedup koloru naAttentionBadgeColor.npx tsc --noEmit✅ = 0. Otwarte: brak przełącznika w configu (zachowanie domyślnie włączone, zgodnie z prośbą); ewentualny gateterm:agentfocusonreturnw przyszłości wymagałby Go +task generate+ restartu.
T32 — Logo Terminal Cloud w powiadomieniu i ikonie na pasku Windows (feedback usera: „nie logo Electrona")
W dev aplikacja działa jako electron.exe, więc Windows przypisywał powiadomienia i ikonę na pasku do „Electron"
(nazwa + logo). Do tego winOpts.icon był ustawiany tylko dla Linuksa — win32/mac szły z ikony exe.
public/logos/terminal-cloud.{png,ico}— NOWE. Zrasteryzowany mark „Terminal Cloud" (mintowy heksagon + prompt>_na ciemnym kafelku), wzięty z SVG zterminalcloudgreeting.tsx. Generator:scratchpad/gen-tc-icon.cjs(przez@resvg/resvg-js, już w node_modules); ICO = 16/24/32/48/64/128/256.emain/tcicon.ts— NOWE (izolowane). Ikona wbudowana jako base64 PNG →nativeImage(data-URL), bo w devpublic/NIE trafia na dysk (Vite serwuje je z pamięci) — ścieżka pliku by się nie rozwiązała.getTerminalCloudIcon()(cache) dla ikony okna i toasta.registerWindowsDevIdentity(dataDir)(dev/win32, best-effort): ustawia własny AppUserModelID + rejestruje jegoDisplayName=„Terminal Cloud" iIconUriwHKCU\Software\Classes\AppUserModelId\…(przezreg add), żeby nagłówek toasta pokazywał naszą nazwę+logo zamiast „Electron"; zapisuje też PNG do data-dir na potrzebyIconUri. Błąd nigdy nie blokuje startu.emain/emain-wsh.ts—handle_notifydostałicon: getTerminalCloudIcon()(Windows:appLogoOverride— logo w toaście).emain/emain-window.ts— w gałęziwin32winOpts.icon = getTerminalCloudIcon()(ikona okna/paska; upstream to pomijał).emain/emain-platform.ts— w dev wołaregisterWindowsDevIdentity(getWaveDataDir())tuż poapp.setName.- Zmiana procesu głównego → wymaga restartu
task dev(emain nie ma HMR).npx tsc --noEmit(frontend+emain) ✅ = 0. - TODO (prod/packaging): pod produkcyjny build można podmienić
build/icon.ico+build/icons/*.pngna wariant TC.
T33 — Prawdziwe loga OpenCode i Antigravity w nagłówku agenta (feedback usera: generyczne kwadraty „O"/„A")
AgentLogo (frontend/app/agent/agentheaderbutton.tsx) renderował SVG tylko dla claude/cursor/codex, a reszta
dostawała monogram (pierwsza litera w kwadracie brand-color). OpenCode i Antigravity miały więc tylko „O"/„A".
frontend/app/asset/opencode-color.svg,antigravity-color.svg— NOWE. Realne marki z LobeHub icons (@lobehub/icons-static-svg, MIT) — to samo źródło i styl co istniejące codex/cursor (viewBox 24×24,fill="currentColor", jeden path). OpenCode = blokowa ramka „O"; Antigravity = bumerang.currentColor→ biorą kolor agenta zagents.json(opencode bursztyn#e8a33d, antigravity mięta#00b4a6), jak codex/cursor.agentheaderbutton.tsx— dwa nowe case'y (opencode,antigravity) wAgentLogo, wzorzec 1:1 jak codex/cursor ([&_svg]:w-[13px] [&_svg]:h-[13px],style={{ color }}) + dwa importy SVGR. Seam dokładnie taki, jak opisuje komentarz komponentu („Drop a<key>-color.svginto app/asset and special-case it here").- Renderer-only → Vite HMR (bez restartu). Atrybucja: LobeHub icons (MIT) — dodane do
NOTICE.tsc✅ = 0.
Selektor agentów w panelu sesji (WaveDropdown) pokazywał tylko kolorowe kropki. Teraz pokazuje te same
marki co nagłówek terminala (claude/codex/cursor/opencode/antigravity), w triggerze i na liście.
frontend/app/agent/agentheaderbutton.tsx—AgentLogowyeksportowany (był lokalny), by dało się go użyć w dropdownie.frontend/app/element/wavedropdown.tsx—WaveDropdownOptiondostał opcjonalneicon?: ReactNode; render (trigger + pozycje listy) ma pierwszeństwo dlaicon, z fallbackiem na kropkę (color). Dzięki temu autonomia-dropdown i opcja „All agents" (bez ikony) działają jak dotąd.frontend/app/element/wavedropdown.scss— nowy slot.wave-dropdown-icon(16×16, wyśrodkowany), żeby wiersze z logo i z kropką były wyrównane.frontend/app/agent/agentsessionspanel.tsx—agentOptionsmapuje realnych agentów zicon: <AgentLogo … />(dane zconfiguredAgents→key/label/color/icon); „All agents" zostaje z szarą kropką.- Renderer-only → Vite HMR (bez restartu).
npx tsc --noEmit✅ = 0.
Drobne poprawki w frontend/app/agent/agentsessionspanel.tsx:
- Segmented control przełożony na Current → Recent → Folder (było Recent → Folder → Current); tylko kolejność
renderu (
["current","recent","folder"]), domyślny widok/logika bez zmian. - Split-button „+ New": pierwsza pozycja „New in another folder…" dostała ten sam sublabel „pick a folder" co „New in new tab" — obie i tak otwierają picker folderu, więc podpis jest teraz na obu (spójność).
- Renderer-only → Vite HMR.
npx tsc --noEmit✅ = 0.
T30 — App menu (hamburger) rysowane in-app zamiast natywnego menu Windows (feedback usera: „ostre krawędzie")
Menu aplikacji (Wave/File/Edit/View/Workspace/Window spod hamburgera) było natywnym menu Electrona
(electron.Menu.popup()), któremu Windows 11 rysuje zaokrąglone rogi — CSS ich nie ruszy. Przepięte na
istniejący fork'owy silnik in-app (WaveContextMenu, T17): ostre krawędzie, mint, spójne z resztą.
emain/emain-menu.ts—makeFullAppMenurozbite namakeFullAppMenuTemplate(zwraca szablon) + cienki wrapper.serializeMenuTemplateidzie bezpośrednio po szablonie (nie po zbudowanymMenu.items) — na Windows część ról app-menu (appMenuitd.) rozwiązuje się inaczej / wypada, co rozjechałoby walk po indeksachmenu.items/szablon. Labelki ról zRoleLabelFallback(appMenu→electron.app.name), resztę rozwiązuje renderer. Akcyjnym liściom nadajeidi rejestruje wappMenuRegistry. IPCget-workspace-appmenu(handle) zwraca drzewo;workspace-appmenu-click(on) odbiera klik: oryginalny closure albodispatchAppMenuRole(role Electrona ręcznie — edit-role nawebContents, quit/close/minimize/zoom/front/togglefullscreen na oknie/app).emain/preload.ts+frontend/types/custom.d.ts—getWorkspaceAppMenu(invoke) iworkspaceAppMenuClick(send).frontend/app/tab/tabbarenv.ts— te dwie metody w zawężonymTabBarEnv.electron.frontend/app/tab/tabbar.tsx—onEllipsisClickpobiera serializowane menu i rysuje przezshowWaveContextMenuzakotwiczone pod hamburgerem;toAppMenuItemskonwertuje drzewo (klik liścia →idz powrotem do main). Fallback: gdygetWorkspaceAppMenuniedostępny (stary preload przed restartem) lub rzuci — wywołuje natywneshowWorkspaceAppMenu, więc przycisk nigdy nie jest „martwy". Natywna ścieżka (workspace-appmenu-show) zostaje też dla builder windows.frontend/preview/mock/preview-electron-api.ts— stuby nowych metod.- Zmiana Go/electron-main → wymaga restartu
task dev. (Objaw „menu nie działa" po pierwszej implementacji = dev z 00:04 miał starydist/main+dist/preloadbez nowych handlerów; renderer dostał nowytabbar.tsxprzez HMR, ale electron-vite nie hot-reloaduje main/preload →getWorkspaceAppMenuundefined → wyjątek łykany przezfireAndForget. Stąd powyższy fallback.)tsc --noEmit(frontend + emain, jeden tsconfig) ✅ = 0. Znane pominięcie: akceleratory (skróty) nie są jeszcze rysowane w in-app menu — do dodania osobnym slotem wWaveContextMenu, jeśli potrzebne.
T29 — Kompaktowy typeahead połączeń (feedback usera: „menu za duże, czcionka większa niż w headerze")
Menu przełącznika połączeń (Connect to (username@host)…, otwierane z przycisku połączenia w nagłówku bloku)
renderowało się większe niż sam nagłówek terminala: search-input 12px, itemy 11px vs 10px headera
(.connection-button/.block-frame-text), do tego luźne odstępy.
frontend/app/modals/typeaheadmodal.scss(plik upstreamowy — zmiana minimalna, stąd notka tutaj): input12px → 11px,.typeahead-item-name11px → 10px(= skala headera) +line-height 14→13, offset ikonytop 2→1,.suggestion-header11px → 10px, padding itemu6px 8px → 4px 8px, gap grup10px → 7px.- Czysto renderer (SCSS) → bez
task generate/restartu; podgląd poCtrl+Shift+R.tsc --noEmit✅ = 0.
Trzy niezależne, czysto rendererowe zmiany (podgląd po Ctrl+Shift+R, bez restartu):
- Przycisk „+" (add tab) —
frontend/app/tab/tabbar.tsx: usunięterounded-mdz klasy przycisku dodawania karty → ostre krawędzie tła hovera, spójnie z redesignem radius-0. - Tekst zakładek —
frontend/app/tab/tab.scss:.nameprzesunięte o 2px w dół (translate3d(-50%, calc(-50% + 2px), 0)) — mintowy keyline 2px na górze aktywnej karty zjadał górę, przez co tekst czytał się za wysoko; teraz optycznie wyśrodkowany. - Split-button „New" (jak „Open ▾" w Windows) —
frontend/app/agent/agentsessionspanel.tsx+agentsessions.scss(.ap-new-split,.ap-btn--caret). Główny New zawsze startuje w bieżącym folderze (global:currentCwd/resolveAgentCwd, gdy brak → picker; tryb folder: blok terminala). Caret ▾ otwiera in-app menu (showWaveContextMenupod splitem): „New in another folder…" (picker → nowy blok w bieżącej zakładce) i „New in new tab" (picker → nowa zakładka + agent w wskazanym folderze).- New in new tab jest cross-renderer-safe (każda zakładka = osobny renderer):
WorkspaceService.CreateTab(zwracatabId, aktywuje) →ObjectService.GetObject(tab)po domyślnyblockids[0]→CreateBlockCommand(wshrpc,tabid+targetaction:"replace") zastępuje domyślny terminal blokiem agenta backendowo (kolejka layoutu per-tab), więc nowa zakładka ma tylko agenta, bez wyścigu z frontendowym layout modelem. Fallback doinsert, gdy domyślnego bloku nie da się odczytać.tsc --noEmit✅ = 0.
- New in new tab jest cross-renderer-safe (każda zakładka = osobny renderer):
Modal „About" spod app-menu (Wave → About) pokazywał branding upstreamu (logo Wave, „Wave Terminal", GitHub/Website/Open Source/Sponsor → wavetermdev, © Command Line Inc.). Apache-2.0 nie wymaga utrzymywania tego okna, a §6 nie daje praw do znaku „Wave" — więc fork powinien mieć własną tożsamość.
frontend/app/modals/about.tsx— przebrandowane na Terminal Cloud (spójnie zterminalcloudgreeting.tsx): mintowy znak heksagonalny zamiastLogoWave, nazwa „Terminal Cloud", tagline „The terminal built for coding agents.", zachowana wersja klienta + kanał. Linki: CloudBots (cloudbots.eu), StatXplorer (statxplorer.com), Built on Wave (waveterm.dev — atrybucja), Contact (mailto). Krawędzie linków ostre (usunięterounded).- Atrybucja / licencja: stopka „Built on Wave Terminal — © Command Line Inc. · Apache-2.0" + zachowany nagłówek
copyright pliku + nienaruszony
LICENSE/NOTICE→ spełnia wymogi Apache-2.0 §4 i NOTICE. - Czysto renderer → podgląd po
Ctrl+Shift+R.tsc --noEmit✅ = 0.
Cel: powiadomienie OS + dźwięk, gdy agent kodujący w terminalu (Claude Code, Codex, OpenCode, Cursor, …) skończy turę, czeka na odpowiedź albo prosi o zgodę — jak wbudowane powiadomienia Codexa, tylko dla wszystkich agentów i przez natywny mechanizm Wave.
Mechanizm (wybrany: „OSC 9 + bell"): wspólnym mianownikiem wszystkich agentów jest
sekwencja ucieczki terminala. Agent emituje OSC 9 (ESC ] 9 ; <tekst> BEL, styl
iTerm2/Codex/OpenCode) lub OSC 777 (ESC ] 777 ; notify ; <tytuł> ; <treść>), a gdy
nie potrafi — dzwonek (\x07). Wave przechwytuje to w terminalu i podnosi
powiadomienie OS przez istniejące NotifyCommand → handle_notify (Electron
Notification). Zero nowego pluminingu powiadomień — tylko most.
frontend/app/view/term/agentnotify.ts— logika mostu:handleOsc9Notification/handleOsc777Notification— parsują payload, klasyfikują zdarzenie z tekstu (approval / waiting / finished), wołająNotifyCommand(route:"electron") z tytułem = etykieta aktywnego agenta (zagents.json), treść = tekst. OSC 9 z prefiksem numerycznym (^\d+;) jest ignorowane (progres ConEmu/Windows Terminal, nie powiadomienie).maybeNotifyOnBell— dzwonek zamieniany na powiadomienie tylko gdy w bloku aktywny jest agent (activeAgentAtom), by nie hałasować przy zwykłym BEL powłoki.- Warunek
unfocused(domyślny) /always, throttle 1.5 s per blok. Dźwięk =silent:!term:agentnotifysound.
frontend/app/view/term/termwrap.ts— import + rejestracja OSC 9 i OSC 777 (tuż za OSC 16162) + jedno wywołaniemaybeNotifyOnBellw istniejącymonBell.pkg/wconfig/settingsconfig.go+pkg/waveobj/wtypemeta.go— 4 klucze (skill add-config):term:agentnotify,term:agentnotifysound,term:agentnotifycondition(unfocused|always),term:agentnotifyautoinstall(ostatni tylko globalny). Defaulty wdefaultconfig/settings.json, docs wdocs/config.mdx.task generatedogenerowałmetaconsts.go(waveobj+wconfig) igotypes.d.ts.cmd/server/main-server.go—go agentnotify.EnsureAllOnStartup()postartConfigWatcher().
pkg/agentnotify/— przy starcie wavesrv iterujeagents.json; dla każdego agenta obecnego na dysku (exec.LookPath cmd:bin/discover:binpath) i jeszcze nie obsłużonego (plik stanuagentnotify-state.jsonw data-dir) idempotentnie dopisuje jego config powiadomień. Nowy agent zainstalowany po Wave dostaje config przy następnym starcie (binarka pojawia się → brak w stanie → jednorazowy provisioning). Bezpieczeństwo: backup<plik>.wave-bakprzed pierwszą zmianą, zapis atomowy, znacznikwave-agent-notifyw naszych wpisach (idempotentna podmiana bez ruszania wpisów użytkownika), gateterm:agentnotifyautoinstall. Best-effort — błędy logowane, nigdy nie blokują startu.provider_codex.go— konserwatywna chirurgia~/.codex/config.toml: ustawia[tui] notification_method="osc9"(trybautoCodexa spada do gołego BEL dla nieznanych terminali — Wave jest „nieznany"). Nie rusza, jeślinotification_methodjuż jest; wstawia klucze pod istniejący[tui](tylko brakujące) albo blok[tui]przed pierwszym[tui.*](rodzic-przed-dzieckiem) / na końcu pliku.provider_opencode.go—attention {enabled,notifications,sound}wtui.json(OpenCode emituje OSC 9 sam). Ścieżka:$OPENCODE_TUI_CONFIG→$XDG_CONFIG_HOME→~/.config/opencode/tui.json.provider_claude.go— hooki w~/.claude/settings.json:Stop→ „finished",Notification/idle_prompt→ „waiting",Notification/permission_prompt→ „approval". Komenda hooka drukujeterminalSequenceJSON z OSC 9 (Claude sam pisze go do PTY — stdout hooka nie trafia do terminala). Wymaga Claude Code ≥ v2.1.141.provider_cursor.go— hookstopw~/.cursor/hooks.json(schema v1) piszący OSC 9 wprost do terminala. Tylkostop(hookibefore*odpalają się przy KAŻDYM narzędziu, nie tylko przy realnej zgodzie → byłby spam).helper.go— skryptwave-agent-notify.{sh,cmd}w data-dir:<mode> <text>(claude= JSON z terminalSequence; inaczej = surowy OSC 9 na/dev/tty/CONOUT$). Centralizuje różnice platformowe zamiast wklejać kruche one-linery do configów. Hardening cytowania (fix z code review):agent:labeljest user-controlled i trafia do komendy hooka, którą shell agenta re-parsuje → ryzyko wstrzyknięcia.shellQuotecytuje poprawnie per-OS (POSIX: idiom'\''— dokładny; Windows:"..."), asanitizeNotifyText(główna granica, wołana wagentLabel) usuwa znaki kontrolne oraz"/%(nieocytowalne bezpiecznie wewnątrz"..."w cmd.exe). Runda 2 (fix z review 2026-07): na Windows label jest wklejany jako%~2jeszcze raz — wewnątrz single-quoted literału PowerShella (i JSON-a w trybie claude) w.cmd, czegoshellQuote(cmd.exe) nie widzi; apostrof zamykał literał (cichy błąd parse → brak powiadomienia, a spreparowany label → wstrzyknięcie PS),\psuł escape JSON.sanitizeNotifyTextusuwa więc dodatkowo'/\tylko na Windows (POSIX obsługuje je dokładnie, test idiomu'\''zostaje). Testy:TestSanitizeNotifyText,TestShellQuoteNoBreakout(case'y'/\gate'owane per-OS).
| Agent | Kanał | „skończył" | „czeka" | „prosi o dostęp" | Pewność |
|---|---|---|---|---|---|
| Codex | wbudowany OSC 9 (config.toml notification_method=osc9) |
✅ | ✅ (= turn) | ✅ | Wysoka (Codex emituje sam; bez osc9 i tak działa przez BEL) |
| OpenCode | wbudowany attention OSC 9 (tui.json) |
✅ | ✅ (= idle) | ✅ permission.asked |
Śr.-wysoka (do potwierdzenia ścieżka tui.json na nie-Linux) |
| Claude Code | hook → terminalSequence OSC 9 |
✅ Stop |
✅ idle_prompt |
✅ permission_prompt |
Średnia (wymaga v2.1.141+; do testu na żywo) |
| Cursor | hook stop → OSC 9 na TTY |
✅ stop |
✅ (= stop) | — (pominięte, byłby spam) | Niska–średnia (Unix /dev/tty OK; Windows CONOUT$ do weryfikacji) |
go vet ./pkg/agentnotify/✅ = 0.go test ./pkg/agentnotify/✅ (merge hooków idempotentny, chirurgia TOML Codexa wstawia[tui]przed[tui.*]i respektuje istniejącynotification_method,hasTomlKeybez false-matchy, backup powstaje).task generate✅ (metaconsts + gotypes spójne).npx tsc --noEmit✅ = 0 błędów.go vet ./cmd/server/ ./pkg/waveobj/ ./pkg/wconfig/✅ = 0.
- Test na żywo w GUI wszystkich czterech ścieżek (jak reszta forka — kod gotowy,
zweryfikowany offline). Priorytet: Claude (wersja hooka
terminalSequence) i Cursor na Windows (CONOUT$). - OSC 9 niesie tylko tekst (brak maszynowego typu) → typ zdarzenia zgadywany z prefiksu; dla Codex/OpenCode tekst jest ich, dla Claude/Cursor kontrolujemy go w hooku.
- OpenCode: potwierdzić lokalizację
tui.jsonpoza Linuxem. Cursor: brak czystego, nie-spamującego sygnału „approval" (hookibefore*odpalają się zawsze) → pominięty. - Codex
notify(zewn. program) tylko dlaagent-turn-complete— nie użyty; OSC 9 pokrywa też approval/plan-mode.
Cel: panel kontroli źródła sterowany folderem roboczym wybranego terminala, z detalami git jak w rozszerzeniu VS Code, oraz diff plików w Monaco.
pkg/git/git.go— cienki wrapper na binarkęgit(exec.CommandContext,cmd.Dir=repoRoot,GIT_TERMINAL_PROMPT=0,GIT_OPTIONAL_LOCKS=0). Funkcje:Status(status --porcelain=v2 --branch -z→ branch/upstream/ahead-behind + per-plik index/worktree/untracked/conflict + remote),Diff(index↔worktree / HEAD↔index),Action(stage/unstage/discard/commit/fetch/pull/push/checkout/ createbranch/stash),Log,ListBranches.- Bezpieczeństwo (z automatycznego security review):
repoPath()blokuje path-traversal nareadWorking/discardos.Remove/Diff;validRef()odrzuca ref-y z wiodącym-/białym znakiem (argv flag smuggling) w checkout/createbranch;remoteUrlToHttp(FE) wpuszcza tylko http/https.
- Bezpieczeństwo (z automatycznego security review):
pkg/wshrpc/wshrpctypes.go— 5 metodGit{Status,Diff,Action,Log,ListBranches}Command- typy
Command Git*Data/Git*Data. Handlery wwshserver.go(timeout 15s, 120s dla akcji sieciowych).task generate(klient TS + gotypes + wshclient.go).
- typy
frontend/app/view/gitscm/gitscm.tsx— widokgitscm(panel SCM: branch + ahead/behind + remote/GitHub, commit box Commit/Commit All + Ctrl+Enter, sekcje Merge/Staged/Changes ze statusami i akcjami per plik, przełącznik brancha, menu „…", zakładka History).gitdiff.tsx— widokgitdiff(Monaco diff, reużywaDiffViewer).gitutil.ts,gitscm.scss.
frontend/app/block/blockregistry.ts— rejestracjagitscm/gitdiff.pkg/wconfig/defaultconfig/widgets.json— widgetgit.frontend/app/workspace/widgets.tsx— seedgitscm:cwdz aktywnego terminala.pkg/waveobj/wtypemeta.go— klucze metagitscm:*/gitdiff:*(→ MetaType).frontend/app/tab/tabcontextmenu.ts— „Open Source Control" (natywne menu Electrona nie da się stylować CSS; zmiana treści).
- Parser porcelain v2 -z sprawdzony na realnym repo (A./.M/?/branch.head).
go vet= 0,tsc= 0, build wavesrv EXIT 0.
Cel: zoom, zwijanie (JSON), i funkcje jak w edytorach — Wave ma już Monaco.
frontend/app/view/codeeditor/codeeditor.tsx—defaultEditorOptions():folding,foldingStrategy:"auto",showFoldingControls:"always",foldingHighlight,mouseWheelZoom(Ctrl+scroll),bracketPairColorization,guides,renderLineHighlight,cursorBlinking:"smooth".frontend/app/view/preview/preview-model.tsx— nagłówek blokucodeeditdostaje szybkie przełączniki Word Wrap + Minimap (stan w akcencie) + Refresh (wcześniej brak).- Diff plików dostarczony jako widok
gitdiff(Pkt 6), bo potrzebuje danych z git.
Cel: cała apka w nowym języku wizualnym (mint #00CED1, ostre krawędzie),
przez zmienne CSS (mały blast-radius — ~90% czyta akcent przez zmienne).
frontend/tailwindsetup.css— skala--color-accent-*+--color-accent/accenthover/ accentbgna mint;--radius:0.frontend/app/theme.scss—--accent-color,--tab-green,--link-color,--button-green-bg/borderna mint;--block-border-radius/--modal-border-radius:0.frontend/app/element/button.scss+ widoczny chrome (taby, inputy, iconbuttony, menu, modale, switcher) — radius 0 (pigułki 50%/999px zostają okrągłe).- Poprawki na żywo: pasek widgetów węższy (
w-10, ikonybase, DEV 18px); „apps" ukryte (SHOW_APPS_WIDGET=false); przycisk Wave AI ✨ ukryty domyślnie (app:hideaibutton ?? truew tabbar/vtabbar); ramka aktywnego bloku mint 0.6 alpha (mniej jaskrawa);caret-color: mintw polach edytowalnych. emain/emain-window.ts— na win32winOpts.roundedCorners = false: ostre rogi całego okna aplikacji (spójne z radius 0). Electron 41 mapuje to naDWMWA_WINDOW_CORNER_PREFERENCE=DWMWCP_DONOTROUND, więc bez FFI. Wymaga restartu Electrona (zmiana w main process).- Ikony prawego paska widgetów na styl Sharp (FA Pro 6.7.2, nowocześniejsze, ostre rogi):
frontend/app/workspace/widgets.tsx— klasawave-widgetbarna kontenerze paska;frontend/app/theme.scss—.wave-widgetbar i.fa-solid { font-family:"Font Awesome 6 Sharp"; font-weight:400 }(400=Sharp Regular kontur; 900=Sharp Solid). Zascope'owane do paska,fa-brands(badge DEV) nietknięte. Zmiana tylko w rendererze — łapie ją hot-reload.
Cel: panel przeglądarki sesji (globalny „All agent sessions", per-terminal
„Agent sessions", „Working folder") dostaje spójny, rozpoznawalny wygląd wzorowany na
projekcie cloud-bench użytkownika — przeniesiony na ciemny motyw Wave.
Sygnatura designu (ciemna adaptacja): akcent mint #00CED1, ostre krawędzie
(radius 0), nagłówki sekcji z mono-etykietą i gradientowym „tickiem" (mint→navy),
heksagonalny mark, JetBrains Mono na metadanych, górna linia tożsamości + delikatny
glow. „New" = jedyny pełny-mint akcent; aktywny segment stonowany (mint-tekst + tint +
podkreślenie), by nie konkurował.
frontend/app/agent/agentsessions.scss— cały system stylów panelu, scoped pod.wave-agentpanel(zero wycieku do reszty zaokrąglonego, zielonego UI Wave). Fonty z zestawu Wave (Inter + JetBrains Mono, ładowane przezfontutil.ts).
frontend/app/agent/agentsessionspanel.tsx— markup przepisany na klasyap-*(bez zmian w logice: te same handlery, stan, efekty i RPCAgentListSessionsCommand).
tsc --noEmit✅ = 0 błędów. Backend nietknięty (zmiana czysto FE — Vite HMR).- Podgląd renderowany 1:1 (standalone HTML) + potwierdzony na żywo w apce (screenshoty od użytkownika): widok Recent, Folder (pogrupowany), Current, Working folder.
Cel: terminal pamięta sesję agenta per okno/blok i przy restarcie ją wznawia.
- Lokalnie = Wersja B (relaunch +
--resume), bo lokalny PTY ginie z restartem (durable w Wave jest celowo remote-only —IsBlockTermDurable()zwraca false dla local/WSL). - Remote = Wersja A (durable reattach, już w Wave) automatycznie: gdy żywy PTY istnieje, init-script się NIE uruchamia (agent dalej działa); gdy PTY padł, zimny start odpala ten sam mechanizm co lokalnie.
Jeden mechanizm (cmd:initscript → wsh agentresume) obsługuje obie wersje.
pkg/agentsession/agentsession.go— rdzeń.ResolveLaunch(agentType, cwd, sessionId, extraArgs...)zwracaLaunchSpec{Cmd, Args, SessionId, Resumed, TranscriptPath}.- Claude (zweryfikowane empirycznie):
--session-id <uuid>= create-only,--resume <uuid>= wznowienie. Jeśli transkrypt~/.claude/projects/<enc-cwd>/<id>.jsonlistnieje → resume; inaczej → (re)use id jako create (stabilne id nawet gdy poprzedni start nie zdążył nic zapisać). EncodeProjectDir: każdy znak spoza[A-Za-z0-9]→-(jeden do jednego, bez zwijania). Zweryfikowane na ~40 realnych katalogach~/.claude/projects.- Codex/Cursor: resume po id gdy zapisane; brak flagi create-with-id → świeży start (id do przechwycenia później; TODO w kodzie).
- Claude (zweryfikowane empirycznie):
pkg/agentsession/agentsession_test.go— 6 testów (kodowanie, create/resume, reuse-id-bez-transkryptu, codex/cursor, nieobsługiwany typ).go test ./pkg/agentsession/✅.cmd/wsh/cmd/wshcmd-agentresume.go— komendawsh agentresume --type <claude|codex|cursor>. Czyta meta bloku (agent:type,agent:sessionid), woła rdzeń, persystujeagent:type/agent:sessionid/agent:transcriptpathprzez SetMeta RPC, exec agenta. Flagi:--type,--print(podgląd komendy bez uruchomienia). Argumenty po--przekazywane do agenta.cmd/wsh/cmd/wshcmd-agentresume-unix.go(//go:build !windows) —syscall.Exec(przejęcie procesu → agent dostaje PTY i sygnały bez forwardingu).cmd/wsh/cmd/wshcmd-agentresume-win.go(//go:build windows) — spawn+wait,signal.Ignore(os.Interrupt)żeby Ctrl+C trafiał do agenta a nie zabijał wsh.- UWAGA: claude na Windows bywa shimem
claude.cmd(npm).exec.LookPathto znajduje; gdyby pojawiły się problemy z uruchamianiem.cmd, owinąć wcmd /c.
- UWAGA: claude na Windows bywa shimem
pkg/waveobj/wtypemeta.go— dodane doMetaTSType:agent:*,agent:type,agent:sessionid,agent:autoresume,agent:transcriptpath. (Blok ~5 linii, oznaczony komentarzem// coding-agent auto-resume.)pkg/waveobj/metaconsts.go— generowany z tagów JSON powyższego structa przeztask generate(cmd/generatego). StałeMetaKey_Agent*pojawiają się automatycznie.pkg/wconfig/defaultconfig/widgets.json— dodanydefwidget@claude(ikona robot,display:order-4.5). Tworzy terminal zagent:type=claude,cmd:initscript="wsh agentresume --type claude". Dla remote-workera (Wersja A) dorzucić doblockdef.meta"term:durable": true.frontend/app/view/term/term-model.ts— wgetContextMenuItems()pozycja "Show Agent Session History" (widoczna gdyagent:transcriptpathustawione), otwiera podgląd transkryptu w blokupreview. (Label po angielsku dla spójności z resztą menu.)frontend/types/gotypes.d.ts— generowany (task generate).
- Kliknij widget claude w sidebarze (albo ustaw na dowolnym terminalu:
wsh setmeta agent:type=claude cmd:initscript="wsh agentresume --type claude"). - Pierwszy start tworzy sesję i zapisuje jej id na bloku.
- Restart bloku/aplikacji → ta sama rozmowa wznowiona (
--resume). - Prawy klik → Show Agent Session History → podgląd
<id>.jsonl. - Remote-worker: ten sam blok +
term:durable:true→ żywy reattach, a po padzie PTY zimne wznowienie.
go test ./pkg/agentsession/✅ (6/6).go build✅ na windows + linux + darwin (cmd/wsh, pkg/agentsession, pkg/waveobj).task generate✅ (typy TS + metaconsts spójne).- Frontend typecheck (
task check:ts) NIE uruchomiony — braknode_modules(wymagayarn install). Zmiana TS to kopia sąsiedniego wzorca; typy z gotypes.d.ts potwierdzone ręcznie (agent:transcriptpath: string,file: string).
-
start-dev.cmd(root repo) — lokalny launcher: ustawia PATH (go+task+zig) i odpalatask dev. Uruchamiać jako WŁASNY proces użytkownika (dwuklik / własny terminal), nie jako zadanie w tle agenta — tło agenta bywa reapowane i apka się zamyka. -
Toolchain zig 0.16.0 —
task devna Windows budujewavesrvz CGO przezCC="zig cc -target x86_64-windows-gnu"(sqlite). Bez zig build pada (cgo: C compiler "zig" not found). Zainstalowany portable wC:\Users\krzys\AppData\Local\Programs\zig\zig-x86_64-windows-0.16.0. To zależność builda (jest w BUILD.md), nie zmiana kodu. -
emain/emain-window.ts— fix błędu dev-only (nie związany z pkt 2, ale blokował uruchomienie).awaitWithDevTimeout()przy przekroczeniuDevInitTimeoutMsodrzucał promise, co przerywałoinitializeTab()PRZEDaddChildView(tabView)→ warstwa taba nigdy nie była dołączana → trwale puste okno na wolnym pierwszym buildzie dev (init taba ~7–12 s > stare 5 s). Zmiana: timeout już nie odrzuca — tylko pokazuje okno+devtools jako pomoc debugową i dalej czeka na realny init (widok dołącza się gdy gotowy). Limit podbity 5000 → 30000 ms. Tylko ścieżkaisDev, produkcja bez zmian.
- Codex/Cursor: przechwycenie nowego session-id po świeżym starcie (skan
~/.codex/sessions, SQLite~/.cursor). Na razie tylko resume-po-id działa pełni. - Windows: drive-letter case (
C:vsc:) w kodowaniu ścieżki — przy niezgodności zprocess.cwd()Claude'a transkrypt może się nie znaleźć (rzadki edge case). - Dedykowany renderer
.jsonl(teraz transkrypt otwiera się jako surowy preview).
Cel: ścieżka pliku w outputcie terminala staje się klikalna (model jak VS Code).
- Zwykły (lewy) klik → otwórz plik w bloku preview w Wave.
- Ctrl/Cmd + lewy klik → otwórz w domyślnej aplikacji OS (
openNativePath, tylko lokalnie). - Prawy klik zostawiony bez zmian → normalne menu kontekstowe Wave.
Historia gestu (zgłoszenia użytkownika): pierwotnie Ctrl+Shift → zmienione na Ctrl+prawy-klik (kolizja Shift) → finalnie odwrócone na zwykły klik = Wave, Ctrl = program OS (bez przechwytywania prawego kliku).
Wave ma WebLinksAddon, ale jego LinkComputer twardo filtruje każde trafienie
przez isUrl() — linkuje wyłącznie URL-e http(s). Stąd osobny provider dla ścieżek.
frontend/app/view/term/termlinks.ts— całość logiki:installFilePathLinks(terminal, blockId)→ rejestrujeILinkProvider;activate(lewy klik) rozróżnia modyfikator: bez Ctrl → preview, z Ctrl/Cmd → aplikacja OS. ZwracaIDisposable[]. (Prawy klik nie jest już przechwytywany.)- Wykrywanie dwuprzebiegowe (
computePathLinks):- Pass 1 — ścieżki bezwzględne ze spacjami (
AbsFilePathRegex): kotwica dysku/UNC/home (C:\,\\,~/), potem leniwie znaki ścieżki wraz ze spacjami do pierwszego rozszerzenia.ext(1–8) + opcjonalny:linia:kol, zakończone granicą (biały znak/cudzysłów/nawias/koniec). Leniwy kwantyfikator*?(nie zachłanny) zapobiega pochłanianiu tekstu PO ścieżce —...plik.md opis v1.2podkreśla tylkoplik.md. Negatywny lookahead(?![A-Za-z]:[\\/])nie pozwala pochłonąć kolejnej ścieżki z nowym dyskiem (C:\a\one.md and C:\b\two.md→ dwa linki). RozwiązujeC:\Projekty\new Wave\...\plik.md(spacja w „new Wave" nie rozrywa tokenu). - Pass 2 — tokeny rozdzielone białym znakiem (
PathRunRegex, jak wcześniej): ścieżki względne,nazwa.ext, ścieżki uniksowe; token to „ścieżka" gdy ma separator/\ albonazwa.ext(ext 1–8 z min. jedną literą, więc1.5odpada). URL-e (://) i flagi (-…) pomijane, końcowe./:obcinane. Zakresy nachodzące na Pass 1 są pomijane (bez podwójnego linkowania).
- Pass 1 — ścieżki bezwzględne ze spacjami (
- Rozwiązywanie: absolutne (POSIX
/…, WindowsC:\…, UNC\\…,~/…) jak jest; względne → sklejone zcmd:cwdbloku; normalizacja./..do forward-slashy. - Walidacja istnienia przy kliknięciu przez
FileInfoCommand(notfound→ no-op); bez RPC na hover (decyzja użytkownika). Remote:formatRemoteUri(path, conn)+connectionprzekazywane do bloku preview; natywne otwarcie tylko lokalnie. - Sekcja „wrapped-line / mapStrIdx" zaadaptowana z
@xterm/addon-web-links(MIT, autorzy xterm.js) — tylko matematyka mapowania indeks→komórka, bo oryginał jest zaszyty naisUrl(). Atrybucja w nagłówku sekcji.
Gdy kliknięta ścieżka NIE istnieje na dysku, zamiast no-op robimy wyszukiwanie po nazwie pliku w projekcie (jak Ctrl+klik na nieistniejącą ścieżkę w VS Code):
- 0 trafień → krótki komunikat w natywnym menu („Nie znaleziono w projekcie: …").
- 1 trafienie → otwiera od razu (podgląd lub aplikacja OS, wg gestu).
- ≥2 trafienia → natywne menu electrona w miejscu kursora (
menu.popup()bez współrzędnych = przy myszy) z listą ścieżek względnych; klik = otwórz.
Rdzeń wyszukiwania — NOWY pakiet Go pkg/filesearch/filesearch.go:
FindProjectRoot(cwd)idzie w górę odcmd:cwdbloku szukając markera projektu (.git/.hg/.svn/go.mod/package.json/Cargo.toml/pyproject.toml/.claude); brak → sam cwd.- Indeks w pamięci (zamiast chodzić po FS przy każdym kliknięciu — jak „Go to File"
w VS Code):
getIndex(root)buduje raz listę wszystkich plików korzenia (filepath.WalkDir, rekurencyjnie — ZWERYFIKOWANE testem: zC:/Projekty/new Waveznajduje zagnieżdżonywaveterm/frontend/app/view/term/termlinks.tsi 8×SKILL.md), cache per-korzeń z TTL 30 s,singleflightprzeciw równoległym budowom. Gdy wynik = 0 i indeks starszy niż 1 s → wymuszony rebuild i ponowna próba (świeżo utworzony plik jest od razu znajdowany). Pomija ciężkie katalogi (node_modules,.git,dist,build,target,.venv,vendor,.idea, … — dotfoldery.claude/.kilocodeNIE pomijane). Limity: 200 000 plików w indeksie, build-deadline 15 s. SearchProjectfiltruje indeks po basename (case-insensitive, dokładna nazwa — „jeden z taką nazwą"); przy klikniętej ścieżce z podkatalogami trafienia z pasującym ogonem idą na górę (sort: suffix→głębokość→długość→leks.), cap 50 wyników.- Lokalnie (dla
connectionset = remote fallback pomijany, bo szuka po lokalnym FS).
Nowy RPC FileSearchCommand(CommandFileSearchData) → CommandFileSearchRtnData
(typy w pkg/wshrpc/wshrpctypes.go, handler w wshserver.go deleguje do
filesearch.SearchProject). task generate dogenerował klienta i typy TS.
go vet ./pkg/filesearch/ = 0; go test ./pkg/filesearch/ (diag rekursji) PASS.
frontend/app/view/term/termwrap.ts— importmakeFilePathLinkProvider+ jedna rejestracja w konstruktorze (tuż zaWebLinksAddon), wynik wrzucony dotoDispose.pkg/wshrpc/wshrpctypes.go— 1 metoda interfejsu + 3 typy (CommandFileSearchData,FileSearchMatch,CommandFileSearchRtnData).pkg/wshrpc/wshserver/wshserver.go— importfilesearch+ handlerFileSearchCommand.- Wygenerowane (przez
task generate):frontend/app/store/wshclientapi.ts,frontend/types/gotypes.d.ts,pkg/wshrpc/wshclient/wshclient.go.
npx tsc --noEmit✅ — zero błędów wtermlinks.ts/termwrap.ts(17 błędów w repo to istniejący dryf typów wfrontend/preview/mock/*iprocessviewer.preview.tsx, niezwiązane).go vet ./pkg/filesearch/ ./pkg/wshrpc/wshserver/✅ = 0.- UWAGA: fallback wyszukiwania wymaga przebudowy backendu (nowy RPC) → restart
task dev(Windows blokuje podmianę działającegowavesrv.x64.exe). - Test na żywo: wymaga świeżej instancji
TermWrap(provider rejestruje się w konstruktorze) → pełny reload renderera (Ctrl+Shift+R) albo nowy blok terminala.
- Sufiks
:linia:koljest obcinany do rozwiązania pliku, ale numer linii nie jest przekazywany do preview (brak skoku do linii) — do dodania, jeśli preview to wspiera. - Ścieżki bezwzględne ze spacjami — ROZWIĄZANE (Pass 1,
AbsFilePathRegex). Pozostaje: katalogi bezwzględne BEZ rozszerzenia oraz uniksowe ścieżki ze spacjami nadal dzielą się na spacji (Pass 1 wymaga.exti kotwicy dysku/UNC/home) — rzadkie, poza zakresem. - Hover podświetla token zawsze (xterm dekoruje link pod kursorem); VS Code pokazuje podkreślenie dopiero z Ctrl — kosmetyczna różnica, do ewentualnego dostrojenia.
T34 — Powiadomienia agentów działają dla wszystkich agentów, nie tylko Claude (feedback usera + konsultacja z Codexem)
User: „działają tylko dla Claude-a, chciałbym dla wszystkich wspieranych agentów". Diagnoza empiryczna
(realne pliki configów na dysku + stringi z binarek codex.exe/opencode.exe + źródło openai/codex +
spec Kitty OSC 99) wykazała, że provisioning i odbiór (renderer) są OK — configi wszystkich 4 agentów
były poprawnie zainstalowane (stan v1). Problem był w emisji per agent: renderer łapał tylko OSC 9/777/bell,
a Claude działał, bo Wave sam autoruje jego escape (hook terminalSequence, bezwarunkowo). Dwa niezależne
root-causy:
- Codex (
pkg/agentnotify/provider_codex.go):notification_method="osc9"był dobry, alenotification_condition="unfocused"zależy od raportowania focusu przez terminal (DEC mode 1004,FocusGained/FocusLost). Codex źródłowo:unfocused == !terminal_focused, flaga aktualizowana z crossterm po włączeniu focus-reportingu. Pod ConPTY/Wave przełączenie okna/zakładki nie robi DOM-blur na textarea xtermu → Codex nigdy nie dostajeFocusLost→ sądzi, że ma focus → nie emituje nic. Fix:notification_condition = "always"(Codex emituje bezwarunkowo, focus gejtuje Wave — jak przy Claude). Emisja OSC 9 z własnego stdout Codeksa jest lepsza na Windows niż zewnętrznynotify = [program](ten musiałby pisać do CONOUT$ z podprocesu). Migracja istniejących instalacji:migrateManagedCodexreconciluje 3 zarządzane klucze (notifications/notification_method/notification_condition) w obrębie bloku z markerem (ManagedMarker→ do pustej linii / innego nagłówka niż[tui]); user-owned config bez markera nietknięty. Marker dopisywany też w ścieżce insert-pod-istniejące-[tui](wcześniej go nie było).provisionVersion1 → 2, żeby stan v1 wymusił re-run i migrację. Testy: v1unfocused→always, idempotencja, unmarked user config, scoping ([other] notifications = falsenietknięte) —go test ./pkg/agentnotify/✅. - OpenCode (
frontend/app/view/term/agentnotify.ts+termwrap.ts):attention.enableddomyślniefalse(nasz configtruepoprawnie włącza), ale desktopowe powiadomienia OpenCode idą przez OSC 99 (protokół Kitty/opentui:ESC]99;i=opentui-notifications:p=<title|body>:e=1:d=<0|1>;<base64>ST), którego Wave nie rejestrował → przepadały. Fix: nowyhandleOsc99Notification+ rejestracja OSC 99 wtermwrap.ts. Parser wg spec Kitty: metadane (kluczekey=valpo:przed pierwszym;), payload base64 przye=1(dekoder UTF-8-safebase64ToString, nieatob),pdomyślnietitle,ddomyślnie „done" (d=0= kolejne chunki), grupowanie chunków po(blok, i), flush na końcu → wspólny lejeknotifyAgentEvent(kropka + chime + OS-notyfikacja). Kontrolnep(p=?capability query,p=close,p=alive, nieznane) są ignorowane. - Korekta guardu OSC 9 (z konsultacji Codeksa): stary
^\d+(;|$)ucinał każdy payload zaczynający się cyfrą, a tekst turn-complete Codeksa to podgląd odpowiedzi asystenta i może zaczynać się od cyfr (np. „1."). Zawężono do samego kształtu progresu ConEmu/WT:^4;\d(subkomenda 4).
Konsultacja z Codexem (agent codex:codex-rescue, read-only): plan CONFIRMED. Potwierdził semantykę
unfocused/always, że osc9 emituje ESC]9;<msg>BEL i że jest lepszy niż notify=[program] na Windows;
skorygował guard OSC 9 (leading-digit), domyślne p/d i grupowanie po i w OSC 99, oraz migrację (reconcile
zarządzanych kluczy, nie tylko condition; zostaw user-owned). Highest-risk (odłożone do testu na żywo): OpenTUI
sonduje możliwości terminala (batch OSC 99 p=? + iTerm2 OSC 1337;Capabilities + Kitty-graphics + DECRQM) i
może nie emitować, jeśli terminal nie odpowie. Emulacji handshake nie robimy (złożona, ryzyko wysłania błędnej
odpowiedzi > korzyść) — OpenTUI ma fallbacki (method_9), a Wave łapie teraz wszystkie trzy metody (9/99/777),
więc niezależnie od wyboru metody escape zostaje przechwycony. Do weryfikacji na żywo; jeśli mimo to nie strzela,
kolejny krok = odpowiedź na sondę p=? (jest już ścieżka zapisu do pty: termWrap.sendDataHandler).
Zakres: Cursor niezainstalowany (pominięty; ma znany haczyk CONOUT$-z-podprocesu na Windows). Antigravity/Gemini
= launch-only, bez mechanizmu notify. Efekt/uruchomienie: zmiana Go (Codex) wymaga restartu task dev —
na starcie wavesrv widzi stan v1 < v2 i automatycznie migruje ~/.codex/config.toml (unfocused→always),
bez ręcznej reinstalacji. Handler OSC 99 (renderer) łapie się po Ctrl+Shift+R (restart też obejmie). tsc ✅ = 0,
go test ./pkg/agentnotify/ ✅.
T35 — Hotfix: crash procesu main na starcie („Cannot access 'WaveHomeVarName' before initialization")
Po restarcie task dev (wymuszonym przez zmianę Go z T34) aplikacja padała na starcie: ReferenceError: Cannot access 'WaveHomeVarName' before initialization w getWaveHomeDir (dist/main/index.js). Regresja z T32
(rebranding taskbara/toastów Windows): w emain/emain-platform.ts blok if (isDev) registerWindowsDevIdentity( getWaveDataDir()) był przed deklaracjami const WaveConfigHomeVarName/WaveDataHomeVarName/WaveHomeVarName.
getWaveDataDir() → getWaveHomeDir() czyta process.env[WaveHomeVarName], więc top-levelowe wywołanie łapało
TDZ stałej (funkcje są hoistowane, const nie). Bomba uśpiona, bo zmiana w emain działa dopiero po pełnym
restarcie task dev — dopiero ten restart ją odpalił. Fix: przeniesiono trzy const Wave*VarName przed blok
if (isDev), z komentarzem-ostrzeżeniem o kolejności. tsc ✅ = 0. Wymaga pełnego restartu task dev (emain
nie ma hot-reloadu).
T36 — Rebrand paczki na „Terminal Cloud" + odcięcie zależności od infrastruktury Wave (build standalone)
Przygotowanie samodzielnej aplikacji: nazwa/ikona Terminal Cloud zamiast Wave oraz — kluczowe — usunięcie
wszystkich runtime'owych „phone-home" do serwerów Wave/Command Line Inc, żeby fork nie wysyłał danych ani nie
podmienił się sam na upstream. Audyt zależności miał zrobić Codex, ale jego sandbox padł w tej sesji
(CreateProcessAsUserW 1312 / brak sandboxPolicy) i słusznie odmówił zmyślania — audyt przeprowadzony ręcznie
(grep całego repo).
Branding (nazwa + ikona + homepage):
package.json:productName„Wave" → „Terminal Cloud" (electron-builder:executableName/artifactName→Terminal Cloud.exe),build.appIddev.commandline.waveterm→eu.cloudbots.terminalcloud(spójnie z devTerminalCloudDevAppIdztcicon.ts),homepage→https://terminal.cloudbots.eu,description→ tagline forka.build/icon.ico→ podmienione napublic/logos/terminal-cloud.ico(7 warstw, do 256×256); oryginał Wave zachowany jakobuild/icon.ico.wave-orig. (macicon.icnszostaje — build macOS poza zakresem.)emain/emain-platform.ts:app.setName(...)→ „Terminal Cloud" / „Terminal Cloud (Dev)" (etykiety menu roli; linia 18app.setName("waveterm/electron")zostaje — to ona wyznacza katalog danych electrona).- Widoczne stringi „Wave Terminal" w shippowanym
emain/:emain-menu.ts„About Wave Terminal" → „About Terminal Cloud";emain.tsdialog wyjścia „…quit Wave Terminal?" → „…quit Terminal Cloud?";updater.tstytuł/treść powiadomienia (nieaktywne, ale spójne).
P0 — odcięcie phone-home (audyt):
- Auto-update → serwer Wave (KRYTYCZNE). Domyślnie WŁĄCZONY (
autoupdate:enabled:true,installonquit:true, co 1h), feeddl.waveterm.dev/releases-w2→ fork sam zainstalowałby release Wave na wierzch. Fix:settings.jsonautoupdate:enabled/installonquit→ false;electron-builder.config.cjspublish.url→https://terminal.cloudbots.eu/releases(nawet ręczne „Check for updates" nie ściągnie Wave). - Telemetria → chmura Wave.
pkg/wcloudwysyła usage/activity/TEvent doapi.waveterm.dev/central+ diagnostic ping doping.waveterm.dev/central.telemetry:enableddomyślnietrue, aSendDiagnosticPingodpala na starcie niezależnie od flagi. Fix dwutorowy:settings.jsontelemetry:enabled→ false ORAZpkg/wcloud/wcloud.gostałeWCloudEndpoint/WCloudPingEndpoint→""(w buildzie prodGetEndpointzwraca puste → każdy send to graceful no-op; dev niedotknięty — czytaWCLOUD_ENDPOINTz env). - Brak
crashReporter, brak rejestracji schematu URLwaveterm://(setAsDefaultProtocolClientnie występuje) — nic do usuwania.
P1 (zanotowane, nie zmienione): domyślne tryby Wave AI (waveai.json, waveai:showcloudmodes:true, default
waveai@balanced) routują przez proxy cfapi.waveterm.dev — ale to opt-in (tylko gdy user użyje panelu AI) i dla
forka i tak nie zadziała (brak kredytów Wave); user może wpiąć własny ai:baseurl. Link FAQ ARM64 w
emain-platform.ts → docs.waveterm.dev (ścieżka tylko przy translacji ARM64 — rzadka). Identyfikatory WaveAI*
(klasy, komendy RPC GetWaveAIChatCommand…) to nazwy wewnętrzne — nie ruszane (inwazyjny refactor + codegen).
Build: task package -- --win dir (toolchain z start-dev.cmd, wavesrv z zig CC). Na Windows electron-builder
zawsze rozpakowuje cache winCodeSign (nawet dla dir/bez podpisu), a jego archiwum ma dowiązania symboliczne
(macowe .dylib) → 7za ... -snld pada „A required privilege is not held by the client" i electron-builder wychodzi
z kodem 1 zanim zdąży odpalić rcedit (ikona + metadane exe). Katalog aplikacji jest już wtedy w pełni złożony
(exe + app.asar + wavesrv.x64.exe + wsh), więc standalone działa — ale exe ma domyślną ikonę/metadane Electrona.
Obejście (zastosowane): ręcznie odpalony node_modules/electron-winstaller/vendor/rcedit.exe na make/win-unpacked/ Terminal Cloud.exe — --set-icon build/icon.ico, --set-version-string ProductName/FileDescription "Terminal Cloud",
CompanyName "CloudBots" (to samo narzędzie, którego użyłby electron-builder). Po tym: ProductName/FileDescription =
Terminal Cloud, ikona Terminal Cloud. Czysty build (auto-rcedit + instalator NSIS) wymaga włączenia Trybu
programisty Windows (albo uruchomienia jako admin), co pozwala 7za tworzyć symlinki. Wynik: make/win-unpacked/ Terminal Cloud.exe — przenośny, samowystarczalny standalone z kompletem poprawek (T34 powiadomienia, T35 fix TDZ,
T36 rebrand+de-Wave). tsc ✅ = 0.
T37 — Domyślna ikona workspace'a: logo Wave → znak Terminal Cloud (maska SVG, bez przebudowy webfonta)
User zauważył zielone logo Wave w przełączniku workspace'ów. To domyślna ikona workspace'a:
pkg/wcore/workspace.go WorkspaceIcons[0] = "custom@wave-logo-solid", renderowana jako fa-kit fa-wave-logo-solid
(glif z customowego webfonta public/fontawesome/webfonts/custom-icons.woff2), tintowana kolorem workspace'a
(WorkspaceColors[0]=#58C142). Fix (renderer-only, bez ruszania .woff2 ani configu): nadpisanie w
frontend/app/theme.scss — reguła .fa-kit.fa-wave-logo-solid::before { content:"" !important; background-color: currentColor; mask: url("data:image/svg+xml,…heks…") center/contain }. Technika mask + currentColor renderuje
mintowy znak Terminal Cloud (heks + > + kreska) i zachowuje tintowanie kolorem workspace'a jak oryginalny glif;
zmiana obejmuje wszystkie miejsca użycia (istniejące + nowe workspace'y), bo nadpisuje samą klasę glifu. Znak to
ten sam mark co w emain/tcicon.ts/greetingu (viewBox 0 0 52 58). Weryfikacja przez Playwright: SVG się ładuje
(naturalSize 134×150), maska maluje 2716 niepustych pikseli, ::before bg = kolor workspace'a. Renderer → w dev
łapie się po Ctrl+Shift+R, w standalone wymaga przebudowy paczki. (Ikona jest jednokolorowa/tintowana — domyślny
workspace pokazuje ją na zielono #58C142; stały mint = zmiana WorkspaceColors[0] lub hardcode koloru w regule.)
T38 — Publikacja forka: publiczne repo GitHub + atrybucja upstreamu + podmiana linków na stronie (prośba usera)
User: „trzeba zrobić repo dla tego projektu i połączyć że to fork dla Wave oraz dać to na stronę gdzie to można pobrać".
Decyzje usera (AskUserQuestion): namespace futureofworktraining, widoczność public, powiązanie = standalone repo + atrybucja (nie GitHub-fork relationship).
Repo: https://github.com/futureofworktraining/terminal-cloud (public, default branch agent-sessions, homepage
terminal.cloudbots.eu, topiki: terminal / wave-terminal / ai-agents / claude-code / electron / developer-tools).
- Remote
origin(był = upstream Wave) przemianowany naupstream(wavetermdev/waveterm); nowyorigin= repo forka. - Repo lokalne było shallow (
.git/shallow, 11 commitów) → pierwszy push padał (did not receive expected object, index-pack failed). Fix:git fetch --unshallow upstream(pełna historia, 2656 commitów), potem pushagent-sessions. Nie pushowano tagów Wave (release'y upstreamu) — strona releases forka zostaje czysta na własne wydania. - Przed pushem: skan sekretów (0 realnych credentiali; publiczny klucz Algolia DocSearch odziedziczony z upstreamu, benign). Commit batcha ~97 plików (rebrand + powiadomienia + attention + context-menu + onboarding).
Atrybucja (żeby „było widać że to fork Wave"):
README.md— banner „Terminal Cloud" na górze (co dodaje fork + link dowavetermdev/waveterm, Apache-2.0, CloudBots,terminal.cloudbots.eu), pod nim---i oryginalny README Wave; nagłówek# Wave Terminal→## Wave Terminal (upstream base).NOTICE— dopisana linijka: fork Wave Terminal na Apache-2.0, modyfikacje developed by CloudBots, patrz MODIFICATIONS.md.
Strona / docs (poza repo git — w root workspace'u): placeholder repo URL github.com/cloudbots/terminal-cloud (zmyślony,
nieistniejący) → github.com/futureofworktraining/terminal-cloud w website/index.html (4×), website/README.md,
docs/getting-started.md, docs/README.md. Sekcja Download zostaje „coming soon" + beta-mailto (brak wydanych binarek;
publiczne repo = pobranie/build ze źródeł). Zob. [[website-and-docs]].
Deploy (Hetzner, [[terminal-cloud-deploy]]): wersja serwerowa index.html różni się od lokalnej — względne ../docs/...
przepisane na absolutne linki GitHub do gałęzi agent-sessions (/blob|/tree/agent-sessions/docs/..., bo serwer serwuje
sam index.html). Wgrane na /opt/terminal-cloud/site/index.html (backup index.html.bak-<ts>, nginx :ro, bez restartu).
DNS już rozwiązuje (terminal.cloudbots.eu → 49.13.145.82, HTTP 200 przez HTTPS/Caddy) — rekord A dodany od czasu
notatek. Live-check: 7× nowy URL, 0 starych placeholderów, repo publicznie HTTP 200 dla anonima.