Files
dotfiles/home_root/.claude/skills/australis-design/ui_kits/terminal/Sidebar.jsx
T
vh 7b16be02bf feat: fold in the portable Claude Code, zsh and git config; add a drift check
Added (all now linked from their live locations):
- home_root/.claude/settings.json. The statusLine command now uses $HOME
  instead of a Linux-only absolute path.
- home_root/.claude/bin/ratecheck and home_root/.claude/skills/australis-design.
- home_config/zsh/claude-config-dir.zsh and home_root/.local/bin/claude-config-dir-init,
  the per-repo Claude Code login helper and its seeding script.
- home_root/.gitconfig and home_config/git/ignore.
- home_root/.zshenv, with the cargo env line guarded so boxes without rust start clean.

link-dotfiles --check reports drift without changing anything: tracked files whose
live copy is no longer a link (stow dry-run conflicts) and links into this repo
that dangle. Exits 1 on drift. Verified against a clean baseline and two planted
drifts, a detached .nanorc and a dangling link.

.mailmap maps every earlier identity to Vuong Hoang: the repo-local 'Your Name'
placeholder config (now removed), host-generated emails, and aider suffixes.

CLAUDE.md: the symlink warning now lists every linked file and points at --check.
README: documents what is and isn't tracked under .claude, the edit rule, and --check.
2026-09-24 09:07:16 -07:00

132 lines
4.2 KiB
React

// Sidebar — workspace switcher + file tree.
const Sidebar = ({ workspace, workspaces, onCycleWorkspace, openFile, activeFile, onOpenFile }) => {
const ws = workspaces[workspace];
return (
<aside style={ssStyles.root}>
<button style={ssStyles.ws} onClick={onCycleWorkspace} title="Switch workspace">
<span style={{ display:'flex', alignItems:'center', gap: 8 }}>
<Icon name="logo" size={20}/>
<span style={ssStyles.wsName}>{ws.name}</span>
</span>
<span style={ssStyles.wsHint}>⌘1</span>
</button>
<div style={ssStyles.search}>
<Icon name="search" size={14} style={{ color: 'var(--fg-3)' }}/>
<input placeholder="Search files…" style={ssStyles.searchInput}/>
<span style={ssStyles.kbd}>⌘K</span>
</div>
<div style={ssStyles.head}>EXPLORER</div>
<div style={ssStyles.tree}>
{ws.tree.map(node => (
<TreeNode key={node.path} node={node} depth={0}
activeFile={activeFile} onOpenFile={onOpenFile}/>
))}
</div>
<div style={ssStyles.foot}>
<Icon name="settings" size={14}/>
<span>settings</span>
</div>
</aside>
);
};
const TreeNode = ({ node, depth, activeFile, onOpenFile }) => {
const [open, setOpen] = React.useState(node.open !== false);
const isFile = !node.children;
const active = activeFile === node.path;
return (
<>
<div
style={{
...ssStyles.row,
paddingLeft: 8 + depth * 14,
background: active ? 'rgba(99,136,216,0.18)' : undefined,
color: active ? '#a4c4ff' : 'var(--fg-1)',
}}
onClick={() => isFile ? onOpenFile(node.path) : setOpen(o => !o)}
>
{!isFile && <Icon name={open ? "chevron-d" : "chevron-r"} size={12} style={{ color:'var(--fg-3)' }}/>}
{isFile && <span style={{ width: 12 }}/>}
<Icon name={isFile ? "file" : "folder"} size={14}
style={{ color: isFile ? 'var(--fg-2)' : '#42dcd1' }}/>
<span style={ssStyles.name}>{node.name}</span>
{node.modified && <span style={ssStyles.modified}>●</span>}
</div>
{!isFile && open && node.children.map(c => (
<TreeNode key={c.path} node={c} depth={depth+1}
activeFile={activeFile} onOpenFile={onOpenFile}/>
))}
</>
);
};
const ssStyles = {
root: {
width: 260, flex: '0 0 260px',
background: '#1f222d',
borderRight: '1px solid #373b46',
display: 'flex', flexDirection: 'column',
fontFamily: 'var(--font-sans)',
color: 'var(--fg-1)',
},
ws: {
display: 'flex', alignItems:'center', justifyContent:'space-between',
padding: '12px 14px',
background: 'transparent', border: 0, borderBottom: '1px solid #373b46',
color: 'var(--fg-0)',
cursor: 'pointer', textAlign: 'left',
fontFamily: 'var(--font-sans)',
},
wsName: { fontSize: 14, fontWeight: 600, letterSpacing: '-0.005em' },
wsHint: {
fontFamily: 'var(--font-mono)', fontSize: 10,
color: 'var(--fg-3)', background: '#1b1e28',
padding: '2px 6px', borderRadius: 4, border: '1px solid #414751',
},
search: {
display: 'flex', alignItems:'center', gap: 8,
margin: '10px 12px',
padding: '6px 10px',
background: '#1b1e28',
border: '1px solid #414751',
borderRadius: 6,
},
searchInput: {
flex: 1, background: 'transparent', border: 0, outline: 'none',
color: 'var(--fg-1)', fontFamily: 'var(--font-sans)', fontSize: 12,
},
kbd: {
fontFamily: 'var(--font-mono)', fontSize: 10,
color: 'var(--fg-3)',
},
head: {
fontFamily: 'var(--font-mono)', fontSize: 10,
textTransform: 'uppercase', letterSpacing: '0.08em',
color: 'var(--fg-3)',
padding: '8px 14px 4px',
},
tree: { flex: 1, overflowY: 'auto', paddingBottom: 8 },
row: {
display: 'flex', alignItems: 'center', gap: 6,
height: 26, padding: '0 10px 0 8px',
fontSize: 13,
cursor: 'pointer',
userSelect: 'none',
},
name: { flex: 1, whiteSpace:'nowrap', overflow:'hidden', textOverflow:'ellipsis' },
modified: { color:'#42dcd1', fontSize: 12 },
foot: {
display:'flex', alignItems:'center', gap:8,
padding: '10px 14px',
borderTop: '1px solid #373b46',
fontSize: 12, color: 'var(--fg-2)',
},
};
window.Sidebar = Sidebar;