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.
This commit is contained in:
@@ -0,0 +1,131 @@
|
||||
// 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;
|
||||
Reference in New Issue
Block a user