Skip to content

Commit 34e4dfd

Browse files
alltomatosclaude
andauthored
refactor(app): dividir session-side-panel.tsx por responsabilidade (#95)
* docs: adicionar diretrizes de tech-debt para split de arquivos grandes Registra a sequência de issues (#13-#19) e a regra de não dividir pages/layout.tsx e context/layout.tsx antes do épico #25 avançar. Co-Authored-By: Claude Sonnet 5 <[email protected]> * refactor(app): dividir session-side-panel.tsx por responsabilidade Extrai a árvore de arquivos (session-side-panel-file-tree.tsx), a barra de abas legada com thisbeyond/solid-dnd (session-side-panel-tabs-legacy.tsx) e a barra de abas v2 com dnd-kit (session-side-panel-tabs-v2.tsx) do arquivo principal, que cai de 867 para 326 linhas e passa a atuar só como orquestrador. Refatoração pura, sem mudança de comportamento. Closes #19 Co-Authored-By: Claude Sonnet 5 <[email protected]> --------- Co-authored-by: Claude Sonnet 5 <[email protected]>
1 parent 313ca9f commit 34e4dfd

5 files changed

Lines changed: 807 additions & 613 deletions

File tree

‎CLAUDE.md‎

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,19 @@
1313
**O que NÃO fazer**:
1414
- Não pedir confirmação extra por toggle ao instalar/remover a skill — o próprio toggle já é a ação.
1515
- Não misturar o gate de "aparece no combobox" com o status bruto de detecção — são coisas diferentes (detecção é sempre completa e reporta todo agente conhecido; o combobox filtra por skill instalada).
16+
17+
## Iniciativa: Tech Debt — divisão de arquivos grandes (`packages/app/src`)
18+
19+
**Responsabilidade**: reduzir arquivos monolíticos de `packages/app/src` em módulos menores por responsabilidade, sem alterar comportamento.
20+
21+
**Rastreamento**: issues rotuladas `tech-debt` no GitHub (alltomatos/opencode) — #13, #14, #15, #16, #17, #18, #19. Trabalho sequencial, uma issue por vez/PR.
22+
23+
**Invariantes**:
24+
- `pages/layout.tsx` (#13) e `context/layout.tsx` (#18) NÃO devem ser divididos antes do épico #25 (descontinuação do layout legado / `newLayoutDesigns()`) avançar — dividir agora é trabalho descartável, pois o épico planeja deletar/reescrever esses arquivos por completo. Priorizar as fatias independentes do épico #25 primeiro: #19, #17, #16, #15, #14.
25+
- Cada issue vira slices pequenos (idealmente um PR por issue, commits menores dentro dela), verificados manualmente no preview antes de prosseguir — nunca um PR monolítico misturando múltiplas issues de split.
26+
- Divisão de arquivo é refatoração pura: nenhuma mudança de comportamento/UX deve acompanhar o split. Se um bug for encontrado durante o split, abrir issue separada — não corrigir dentro do PR de refactor.
27+
- Após cada split, `/qa-analyst` deve confrontar app antes/depois (sem regressão visual/funcional) antes de abrir PR.
28+
29+
**O que NÃO fazer**:
30+
- Não iniciar #13 ou #18 sem antes checar o status do épico #25.
31+
- Não abrir uma única issue/PR cobrindo dois arquivos grandes ao mesmo tempo — mantém rastreabilidade 1:1 com a issue do GitHub.
Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
import { Match, Show, Switch } from "solid-js"
2+
import { Tabs } from "@opencode-ai/ui/tabs"
3+
import { ResizeHandle } from "@opencode-ai/ui/resize-handle"
4+
5+
import FileTree from "@/components/file-tree"
6+
import { useFile } from "@/context/file"
7+
import { useLanguage } from "@/context/language"
8+
import { useLayout } from "@/context/layout"
9+
import { useSettings } from "@/context/settings"
10+
import type { Sizing } from "@/pages/session/helpers"
11+
12+
const FILE_TREE_WIDTH_MIN = 240
13+
14+
export function SessionSidePanelFileTree(props: {
15+
reviewOpen: () => boolean
16+
fileOpen: () => boolean
17+
treeWidth: () => string
18+
size: Sizing
19+
reviewCount: () => number
20+
hasReview: () => boolean
21+
diffsReady: () => boolean
22+
diffFiles: () => string[]
23+
kinds: () => Map<string, "add" | "del" | "mix">
24+
activeDiff?: string
25+
focusReviewDiff: (path: string) => void
26+
onOpenTab: (tab: string) => void
27+
}) {
28+
const layout = useLayout()
29+
const settings = useSettings()
30+
const file = useFile()
31+
const language = useLanguage()
32+
33+
const fileTreeTab = () => layout.fileTree.tab()
34+
const fileTreeWidth = () => Math.max(FILE_TREE_WIDTH_MIN, layout.fileTree.width())
35+
36+
const setFileTreeTabValue = (value: string) => {
37+
if (value !== "changes" && value !== "all") return
38+
layout.fileTree.setTab(value)
39+
}
40+
41+
const nofiles = () => {
42+
const state = file.tree.state("")
43+
if (!state?.loaded) return false
44+
return file.tree.children("").length === 0
45+
}
46+
47+
const empty = (msg: string) => (
48+
<div class="h-full flex flex-col">
49+
<div class="h-6 shrink-0" aria-hidden />
50+
<div class="flex-1 pb-64 flex items-center justify-center text-center">
51+
<div class="text-12-regular text-text-weak">{msg}</div>
52+
</div>
53+
</div>
54+
)
55+
56+
return (
57+
<Show when={props.fileOpen()}>
58+
<div
59+
id="file-tree-panel"
60+
class="relative min-w-0 h-full shrink-0 overflow-hidden"
61+
classList={{
62+
"transition-[width] duration-200 ease-[cubic-bezier(0.22,1,0.36,1)] will-change-[width] motion-reduce:transition-none":
63+
!props.size.active(),
64+
}}
65+
style={{ width: props.treeWidth() }}
66+
>
67+
<div
68+
class="h-full flex flex-col overflow-hidden group/filetree"
69+
classList={{ "border-l border-border-weaker-base": props.reviewOpen() }}
70+
>
71+
<Tabs
72+
variant="pill"
73+
value={fileTreeTab()}
74+
onChange={setFileTreeTabValue}
75+
class="h-full"
76+
data-scope="filetree"
77+
>
78+
<Tabs.List>
79+
<Tabs.Trigger value="changes" class="flex-1" classes={{ button: "w-full" }}>
80+
<Show
81+
when={settings.general.newLayoutDesigns()}
82+
fallback={
83+
<>
84+
{props.reviewCount()}{" "}
85+
{language.t(
86+
props.reviewCount() === 1 ? "session.review.change.one" : "session.review.change.other",
87+
)}
88+
</>
89+
}
90+
>
91+
{language.t("session.review.filesChanged", { count: props.reviewCount() })}
92+
</Show>
93+
</Tabs.Trigger>
94+
<Tabs.Trigger value="all" class="flex-1" classes={{ button: "w-full" }}>
95+
{language.t("session.files.all")}
96+
</Tabs.Trigger>
97+
</Tabs.List>
98+
<Show when={fileTreeTab() === "changes"}>
99+
<Tabs.Content value="changes" class="bg-background-stronger px-3 py-0">
100+
<Switch>
101+
<Match when={props.hasReview() || !props.diffsReady()}>
102+
<Show
103+
when={props.diffsReady()}
104+
fallback={
105+
<div class="px-2 py-2 text-12-regular text-text-weak">
106+
{language.t("common.loading")}
107+
{language.t("common.loading.ellipsis")}
108+
</div>
109+
}
110+
>
111+
<FileTree
112+
path=""
113+
class="pt-3"
114+
allowed={props.diffFiles()}
115+
kinds={props.kinds()}
116+
draggable={false}
117+
active={props.activeDiff}
118+
onFileClick={(node) => props.focusReviewDiff(node.path)}
119+
/>
120+
</Show>
121+
</Match>
122+
</Switch>
123+
</Tabs.Content>
124+
</Show>
125+
<Show when={fileTreeTab() === "all"}>
126+
<Tabs.Content value="all" class="bg-background-stronger px-3 py-0">
127+
<Switch>
128+
<Match when={nofiles()}>{empty(language.t("session.files.empty"))}</Match>
129+
<Match when={true}>
130+
<FileTree
131+
path=""
132+
class="pt-3"
133+
modified={props.diffFiles()}
134+
kinds={props.kinds()}
135+
onFileClick={(node) => props.onOpenTab(file.tab(node.path))}
136+
/>
137+
</Match>
138+
</Switch>
139+
</Tabs.Content>
140+
</Show>
141+
</Tabs>
142+
</div>
143+
<Show when={props.fileOpen()}>
144+
<div onPointerDown={() => props.size.start()}>
145+
<ResizeHandle
146+
direction="horizontal"
147+
edge="start"
148+
size={fileTreeWidth()}
149+
min={FILE_TREE_WIDTH_MIN}
150+
max={480}
151+
onResize={(width) => {
152+
props.size.touch()
153+
layout.fileTree.resize(width)
154+
}}
155+
/>
156+
</div>
157+
</Show>
158+
</div>
159+
</Show>
160+
)
161+
}

0 commit comments

Comments
 (0)