Compare commits
4 Commits
077925f1d7
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 41751a1a58 | |||
| ce22561813 | |||
| 436522f3b4 | |||
| 072920ff0e |
@@ -0,0 +1,35 @@
|
||||
{
|
||||
"$comment": "Team-shared restricted options. Destructive/irreversible commands are DENIED so Claude proposes them for the user to run instead of executing them. Authoritative policy: see the 'Destructive operations — NEVER delete' section of CLAUDE.md.",
|
||||
"permissions": {
|
||||
"deny": [
|
||||
"Bash(rm:*)",
|
||||
"Bash(rmdir:*)",
|
||||
"Bash(git branch -d:*)",
|
||||
"Bash(git branch -D:*)",
|
||||
"Bash(git branch --delete:*)",
|
||||
"Bash(git push --delete:*)",
|
||||
"Bash(git push -d:*)",
|
||||
"Bash(git push origin --delete:*)",
|
||||
"Bash(git push github --delete:*)",
|
||||
"Bash(git push origin -d:*)",
|
||||
"Bash(git push github -d:*)",
|
||||
"Bash(git tag -d:*)",
|
||||
"Bash(git tag --delete:*)",
|
||||
"Bash(git remote remove:*)",
|
||||
"Bash(git remote rm:*)",
|
||||
"Bash(git reset --hard:*)",
|
||||
"Bash(git clean -f:*)",
|
||||
"Bash(git clean -d:*)",
|
||||
"Bash(git clean -x:*)",
|
||||
"Bash(git push --force:*)",
|
||||
"Bash(git push -f:*)",
|
||||
"Bash(git push --force-with-lease:*)",
|
||||
"Bash(git checkout --:*)",
|
||||
"PowerShell(Remove-Item:*)",
|
||||
"PowerShell(rm:*)",
|
||||
"PowerShell(del:*)",
|
||||
"PowerShell(rmdir:*)",
|
||||
"PowerShell(Clear-Content:*)"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,24 @@
|
||||
|
||||
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
||||
|
||||
## Destructive operations — NEVER delete (hard rule)
|
||||
|
||||
**Claude must never EXECUTE a destructive or irreversible command. Always ask, and hand the user the exact command(s) to run themselves.**
|
||||
|
||||
This covers (non-exhaustively):
|
||||
- Deleting files/directories: `rm`, `rm -rf`, `rmdir`, `del`, `Remove-Item`.
|
||||
- Deleting branches: `git branch -d` / `-D`, `git push --delete`, `git push <remote> :branch`.
|
||||
- Deleting tags/remotes: `git tag -d`, `git remote remove` / `rm`.
|
||||
- Discarding work: `git reset --hard`, `git checkout -- <path>`, `git clean -f`.
|
||||
- Force-pushing: `git push -f` / `--force` / `--force-with-lease`.
|
||||
- Dropping data: `DROP`, `TRUNCATE`, destructive migrations.
|
||||
|
||||
Instead: print the command(s) in a fenced block with a one-line note on what each does and what it affects, and let the **user run them**. Never run them yourself, even when the desired outcome is clear — `rm` and `-d` are **prompted, never executed**.
|
||||
|
||||
Leave regular branches alone (`main`, the active sprint branch) unless the user explicitly names them. Before calling any branch "stale", prove containment (`git branch --merged`, 0 unique commits) and report that evidence — do not act on it.
|
||||
|
||||
These are also enforced as `permissions.deny` rules in `.claude/settings.json` (defense-in-depth), but this behavioral rule is authoritative and covers cases the patterns can't.
|
||||
|
||||
## Commands
|
||||
|
||||
```bash
|
||||
|
||||
+5
-5
@@ -589,7 +589,7 @@ export default function App() {
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={`min-h-screen bg-surface text-white p-3${jamView ? ' xl:h-screen xl:overflow-hidden xl:flex xl:flex-col' : ''}`}>
|
||||
<div className={`min-h-screen bg-surface text-white p-3 max-sm:p-2${jamView ? ' xl:h-screen xl:overflow-hidden xl:flex xl:flex-col' : ''}`}>
|
||||
|
||||
{/* ── Header ── */}
|
||||
<header className="mb-2 flex items-center justify-between gap-2 flex-wrap">
|
||||
@@ -628,7 +628,7 @@ export default function App() {
|
||||
</header>
|
||||
|
||||
{/* ── Controls bar ── */}
|
||||
<div className="mb-2 flex flex-wrap gap-2 items-center p-2 bg-panel border border-border rounded-xl">
|
||||
<div className="mb-2 relative flex flex-wrap gap-2 items-center p-2 max-sm:p-1.5 bg-panel border border-border rounded-xl">
|
||||
|
||||
{/* Instrument select */}
|
||||
<div className="relative">
|
||||
@@ -731,7 +731,7 @@ export default function App() {
|
||||
)}
|
||||
|
||||
{/* ── Jam roulette (L-60, jam-roulette.md §1.1/§1.2) ── */}
|
||||
<div className="relative ml-auto" ref={rouletteRef}>
|
||||
<div className="static sm:relative ml-auto" ref={rouletteRef}>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setRouletteMenuOpen(o => !o)}
|
||||
@@ -910,7 +910,7 @@ export default function App() {
|
||||
<span>{showDebug ? '▲' : '▼'}</span>
|
||||
</button>
|
||||
{showDebug && (
|
||||
<div className="border-t border-border p-4">
|
||||
<div className="border-t border-border p-4 max-sm:p-3">
|
||||
<Suspense fallback={null}>
|
||||
<DebugView
|
||||
chroma={debugChroma}
|
||||
@@ -937,7 +937,7 @@ export default function App() {
|
||||
<span>{showDrumView ? '▲' : '▼'}</span>
|
||||
</button>
|
||||
{showDrumView && (
|
||||
<div className="border-t border-border p-4">
|
||||
<div className="border-t border-border p-4 max-sm:p-3">
|
||||
<Suspense fallback={null}>
|
||||
<DrumView waveform={debugWaveform} bpm={bpm} />
|
||||
</Suspense>
|
||||
|
||||
@@ -757,13 +757,13 @@ export default function ChordDetailModal({ chord, onClose, onChordClick, keyInfo
|
||||
|
||||
return (
|
||||
<div
|
||||
className="fixed inset-0 z-50 flex items-start justify-center bg-black/70 backdrop-blur-sm p-4 overflow-y-auto"
|
||||
className="fixed inset-0 z-50 flex items-start justify-center bg-black/70 backdrop-blur-sm p-4 max-sm:p-2 overflow-y-auto"
|
||||
onClick={e => { if (e.target === e.currentTarget) onClose() }}
|
||||
>
|
||||
<div className="w-full max-w-3xl bg-panel border border-border rounded-2xl shadow-2xl mt-8 mb-8">
|
||||
<div className="w-full max-w-3xl bg-panel border border-border rounded-2xl shadow-2xl mt-8 mb-8 max-sm:my-2">
|
||||
|
||||
{/* Header */}
|
||||
<div className="flex items-center justify-between px-6 py-4 border-b border-border">
|
||||
<div className="flex items-center justify-between px-6 py-4 max-sm:px-4 max-sm:py-3 border-b border-border">
|
||||
<div>
|
||||
<h2 className="text-3xl font-black text-accent leading-none">{chord}</h2>
|
||||
<p className="text-xs text-gray-500 mt-0.5">{typeName} chord · tap a voicing to study it</p>
|
||||
@@ -778,7 +778,7 @@ export default function ChordDetailModal({ chord, onClose, onChordClick, keyInfo
|
||||
</div>
|
||||
|
||||
{/* Tab bar */}
|
||||
<div className="flex gap-1 px-6 pt-4 overflow-x-auto">
|
||||
<div className="flex gap-1 px-6 pt-4 max-sm:px-4 max-sm:pt-3 overflow-x-auto">
|
||||
{[
|
||||
{ key: 'guitar', label: '🎸 Guitar' },
|
||||
{ key: 'piano', label: '🎹 Piano' },
|
||||
@@ -800,7 +800,7 @@ export default function ChordDetailModal({ chord, onClose, onChordClick, keyInfo
|
||||
</div>
|
||||
|
||||
{/* Content */}
|
||||
<div className="px-6 py-5">
|
||||
<div className="px-6 py-5 max-sm:px-4 max-sm:py-4">
|
||||
{tab === 'guitar' && <GuitarTab chordName={chord} />}
|
||||
{tab === 'piano' && <PianoTab chordName={chord} />}
|
||||
{tab === 'theory' && <TheoryTab chordName={chord} />}
|
||||
|
||||
@@ -24,9 +24,9 @@
|
||||
// Standard tuning open-string pitch classes, indexed low-E (0) → high-E (5).
|
||||
const OPEN_PCS = [4, 9, 2, 7, 11, 4] // E A D G B E
|
||||
|
||||
// We render strings top→bottom as high-E first (matches Fretboard.jsx idiom),
|
||||
// so display index 0 = high E, 5 = low E. Data arrays are low-E first, so the
|
||||
// data index for display row `di` is `5 - di`.
|
||||
// We render strings left→right as low-E first (chord-diagram convention:
|
||||
// 6th string on the left, 1st on the right). Data arrays are also low-E first,
|
||||
// so display row index == data index.
|
||||
|
||||
const ACCENT = '#a855f7' // chord-tone tier (root highlight)
|
||||
const DOT = '#e5e7eb' // non-root finger dots (light gray, AA on dark board)
|
||||
@@ -114,10 +114,10 @@ function buildRows(resolved) {
|
||||
// Root pitch class for colouring.
|
||||
const rootPc = resolved.rootPc
|
||||
|
||||
// Convert to display rows (high-E first → reverse of low-E-first).
|
||||
// Convert to display rows (low-E first — display index == data index).
|
||||
const rows = []
|
||||
for (let di = 0; di < NUM_STRINGS; di++) {
|
||||
const dataIdx = NUM_STRINGS - 1 - di
|
||||
const dataIdx = di
|
||||
const f = absLowE[dataIdx]
|
||||
const stringPc = (OPEN_PCS[dataIdx] + (typeof f === 'number' ? f : 0)) % 12
|
||||
const isRoot = typeof f === 'number' && f >= 0 && stringPc === rootPc
|
||||
@@ -153,7 +153,7 @@ export default function ChordDiagram({
|
||||
const svgW = padL + gridW + padR
|
||||
const svgH = padT + gridH + padB
|
||||
|
||||
const stringX = si => padL + si * sw // si: 0 = high E (left) … 5 = low E
|
||||
const stringX = si => padL + si * sw // si: 0 = low E (left) … 5 = high E
|
||||
const fretY = fi => padT + fi * cell // fi: 0 = top line … NUM_FRETS
|
||||
|
||||
if (!built) {
|
||||
@@ -235,14 +235,14 @@ export default function ChordDiagram({
|
||||
x2={stringX(si)}
|
||||
y2={fretY(NUM_FRETS)}
|
||||
stroke={STRING_COL}
|
||||
strokeWidth={(si >= 4 ? 1.4 : si >= 2 ? 1.1 : 0.8) * scale}
|
||||
strokeWidth={(si < 2 ? 1.4 : si < 4 ? 1.1 : 0.8) * scale}
|
||||
/>
|
||||
))}
|
||||
|
||||
{/* Per-string markers: mute ✕ / open ○ above the nut, dots on the grid */}
|
||||
{rows.map((row, si) => {
|
||||
const x = stringX(si)
|
||||
const dataIdx = NUM_STRINGS - 1 - si
|
||||
const dataIdx = si
|
||||
const finger = fingers ? fingers[dataIdx] : 0
|
||||
|
||||
// Muted string → ✕ above the board.
|
||||
|
||||
@@ -856,7 +856,7 @@ export function KnowledgeDock({ keyInfo, chordHistory = [], currentChord, onChor
|
||||
<div className="border-t border-border flex flex-col" style={{ height: '70vh' }}>
|
||||
|
||||
{/* ── Section nav (Knowledge Center pills, D-20 §1) ── */}
|
||||
<div className="flex items-center gap-1 px-4 py-2 border-b border-border overflow-x-auto">
|
||||
<div className="flex items-center gap-1 px-4 py-2 max-sm:px-3 border-b border-border overflow-x-auto">
|
||||
{SECTIONS.map(s => {
|
||||
const active = section === s.id
|
||||
return (
|
||||
@@ -879,7 +879,7 @@ export function KnowledgeDock({ keyInfo, chordHistory = [], currentChord, onChor
|
||||
|
||||
{/* ── Explore — KB progression browser + famous progressions ── */}
|
||||
{section === 'explore' && (
|
||||
<div className="flex-1 min-h-0 p-4 overflow-auto">
|
||||
<div className="flex-1 min-h-0 p-4 max-sm:p-3 overflow-auto">
|
||||
<ExploreSection
|
||||
keyInfo={keyInfo}
|
||||
levels={levels}
|
||||
@@ -891,7 +891,7 @@ export function KnowledgeDock({ keyInfo, chordHistory = [], currentChord, onChor
|
||||
|
||||
{/* ── Voicings — picker (follows live chord) → VoicingBrowser ── */}
|
||||
{section === 'voicings' && (
|
||||
<div className="flex-1 min-h-0 p-4 overflow-auto">
|
||||
<div className="flex-1 min-h-0 p-4 max-sm:p-3 overflow-auto">
|
||||
<VoicingsSection
|
||||
keyInfo={keyInfo}
|
||||
chordHistory={chordHistory}
|
||||
@@ -903,7 +903,7 @@ export function KnowledgeDock({ keyInfo, chordHistory = [], currentChord, onChor
|
||||
|
||||
{/* ── Licks & Techniques — per-style LickCard grid ── */}
|
||||
{section === 'licks' && (
|
||||
<div className="flex-1 min-h-0 p-4 overflow-auto">
|
||||
<div className="flex-1 min-h-0 p-4 max-sm:p-3 overflow-auto">
|
||||
<LicksSection styles={styles} levels={levels} onToggleLevel={toggleLevel} />
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -156,10 +156,10 @@ function SubRow({ sub, instrument, onChordClick }) {
|
||||
const tag = CATEGORY_TAG[sub.category] ?? sub.category
|
||||
return (
|
||||
<div
|
||||
className={`flex items-center gap-3 rounded-lg border border-border bg-border/30 p-2 ${ROW_MIN_H[instrument] ?? ROW_MIN_H.guitar}`}
|
||||
className={`flex flex-col gap-2 sm:flex-row sm:items-center sm:gap-3 rounded-lg border border-border bg-border/30 p-2 ${ROW_MIN_H[instrument] ?? ROW_MIN_H.guitar}`}
|
||||
>
|
||||
{/* Left identity block (~180px) */}
|
||||
<div className="flex w-[180px] shrink-0 flex-col gap-1">
|
||||
<div className="flex w-full sm:w-[180px] shrink-0 flex-col gap-1">
|
||||
<div className="flex items-center gap-1.5">
|
||||
<button
|
||||
type="button"
|
||||
|
||||
Reference in New Issue
Block a user