diff --git a/.beads/.gitignore b/.beads/.gitignore index f32e807..e6fb002 100644 --- a/.beads/.gitignore +++ b/.beads/.gitignore @@ -1,11 +1,49 @@ -# Database -*.db -*.db-shm -*.db-wal - -# Lock files -*.lock +# Dolt database (managed by Dolt, not git) +dolt/ +dolt-access.lock -# Temporary +# Runtime files +bd.sock +bd.sock.startlock +sync-state.json last-touched -*.tmp + +# Local version tracking (prevents upgrade notification spam after git ops) +.local_version + +# Worktree redirect file (contains relative path to main repo's .beads/) +# Must not be committed as paths would be wrong in other clones +redirect + +# Sync state (local-only, per-machine) +# These files are machine-specific and should not be shared across clones +.sync.lock +export-state/ + +# Ephemeral store (SQLite - wisps/molecules, intentionally not versioned) +ephemeral.sqlite3 +ephemeral.sqlite3-journal +ephemeral.sqlite3-wal +ephemeral.sqlite3-shm + +# Dolt server management (auto-started by bd) +dolt-server.pid +dolt-server.log +dolt-server.lock + +# Legacy files (from pre-Dolt versions) +*.db +*.db?* +*.db-journal +*.db-wal +*.db-shm +db.sqlite +bd.db +daemon.lock +daemon.log +daemon-*.log.gz +daemon.pid +# NOTE: Do NOT add negation patterns here. +# They would override fork protection in .git/info/exclude. +# Config files (metadata.json, config.yaml) are tracked by git by default +# since no pattern above ignores them. diff --git a/.beads/README.md b/.beads/README.md new file mode 100644 index 0000000..0efd932 --- /dev/null +++ b/.beads/README.md @@ -0,0 +1,81 @@ +# Beads - AI-Native Issue Tracking + +Welcome to Beads! This repository uses **Beads** for issue tracking - a modern, AI-native tool designed to live directly in your codebase alongside your code. + +## What is Beads? + +Beads is issue tracking that lives in your repo, making it perfect for AI coding agents and developers who want their issues close to their code. No web UI required - everything works through the CLI and integrates seamlessly with git. + +**Learn more:** [github.com/steveyegge/beads](https://github.com/steveyegge/beads) + +## Quick Start + +### Essential Commands + +```bash +# Create new issues +bd create "Add user authentication" + +# View all issues +bd list + +# View issue details +bd show + +# Update issue status +bd update --claim +bd update --status done + +# Sync with Dolt remote +bd dolt push +``` + +### Working with Issues + +Issues in Beads are: +- **Git-native**: Stored in `.beads/issues.jsonl` and synced like code +- **AI-friendly**: CLI-first design works perfectly with AI coding agents +- **Branch-aware**: Issues can follow your branch workflow +- **Always in sync**: Auto-syncs with your commits + +## Why Beads? + +✨ **AI-Native Design** +- Built specifically for AI-assisted development workflows +- CLI-first interface works seamlessly with AI coding agents +- No context switching to web UIs + +🚀 **Developer Focused** +- Issues live in your repo, right next to your code +- Works offline, syncs when you push +- Fast, lightweight, and stays out of your way + +🔧 **Git Integration** +- Automatic sync with git commits +- Branch-aware issue tracking +- Intelligent JSONL merge resolution + +## Get Started with Beads + +Try Beads in your own projects: + +```bash +# Install Beads +curl -sSL https://raw.githubusercontent.com/steveyegge/beads/main/scripts/install.sh | bash + +# Initialize in your repo +bd init + +# Create your first issue +bd create "Try out Beads" +``` + +## Learn More + +- **Documentation**: [github.com/steveyegge/beads/docs](https://github.com/steveyegge/beads/tree/main/docs) +- **Quick Start Guide**: Run `bd quickstart` +- **Examples**: [github.com/steveyegge/beads/examples](https://github.com/steveyegge/beads/tree/main/examples) + +--- + +*Beads: Issue tracking that moves at the speed of thought* ⚡ diff --git a/.beads/backup/backup_state.json b/.beads/backup/backup_state.json new file mode 100644 index 0000000..fd13c2b --- /dev/null +++ b/.beads/backup/backup_state.json @@ -0,0 +1,13 @@ +{ + "last_dolt_commit": "dt382vgdu020rl2b1evhg03jmudk3er7", + "last_event_id": 0, + "timestamp": "2026-03-08T11:32:24.600708Z", + "counts": { + "issues": 2, + "events": 2, + "comments": 0, + "dependencies": 0, + "labels": 0, + "config": 11 + } +} \ No newline at end of file diff --git a/.beads/backup/comments.jsonl b/.beads/backup/comments.jsonl new file mode 100644 index 0000000..e69de29 diff --git a/.beads/backup/config.jsonl b/.beads/backup/config.jsonl new file mode 100644 index 0000000..62aded3 --- /dev/null +++ b/.beads/backup/config.jsonl @@ -0,0 +1,11 @@ +{"key":"auto_compact_enabled","value":"false"} +{"key":"compact_batch_size","value":"50"} +{"key":"compact_parallel_workers","value":"5"} +{"key":"compact_tier1_days","value":"30"} +{"key":"compact_tier1_dep_levels","value":"2"} +{"key":"compact_tier2_commits","value":"100"} +{"key":"compact_tier2_days","value":"90"} +{"key":"compact_tier2_dep_levels","value":"5"} +{"key":"compaction_enabled","value":"false"} +{"key":"issue_prefix","value":"Ghostly"} +{"key":"schema_version","value":"6"} diff --git a/.beads/backup/dependencies.jsonl b/.beads/backup/dependencies.jsonl new file mode 100644 index 0000000..e69de29 diff --git a/.beads/backup/events.jsonl b/.beads/backup/events.jsonl new file mode 100644 index 0000000..fd7f882 --- /dev/null +++ b/.beads/backup/events.jsonl @@ -0,0 +1,2 @@ +{"actor":"Gabko14","comment":null,"created_at":"2026-03-04T16:17:23Z","event_type":"created","id":1,"issue_id":"Ghostly-7yl","new_value":"","old_value":""} +{"actor":"Gabko14","comment":null,"created_at":"2026-03-08T12:32:24Z","event_type":"created","id":2,"issue_id":"Ghostly-2he","new_value":"","old_value":""} diff --git a/.beads/backup/issues.jsonl b/.beads/backup/issues.jsonl new file mode 100644 index 0000000..4402940 --- /dev/null +++ b/.beads/backup/issues.jsonl @@ -0,0 +1,2 @@ +{"acceptance_criteria":"","actor":"","agent_state":"","assignee":null,"await_id":"","await_type":"","close_reason":"","closed_at":null,"closed_by_session":"","compacted_at":null,"compacted_at_commit":null,"compaction_level":0,"content_hash":"5a5773256587c0b7c2efc2697030cdb1c12c2b4c9179b3b91d399c3905b31976","created_at":"2026-03-08T11:32:25Z","created_by":"Gabko14","crystallizes":0,"defer_until":null,"description":"When opening Ghostly via the menu bar icon, the text editor doesn't receive keyboard focus — user must click inside the editor before typing. Root cause: ContentView sets focus via .onAppear which only fires once (MenuBarExtra .window style reuses the view hierarchy). On subsequent opens, isMenuPresented becomes true but nothing reasserts isTextEditorFocused. Fix: add .onChange(of: appState.isMenuPresented) to set isTextEditorFocused = true when popover opens. Key files: ContentView.swift (focus state), GhostlyTextEditor.swift (applyFocusIfNeeded), AppState.swift (isMenuPresented).","design":"","due_at":null,"ephemeral":0,"estimated_minutes":null,"event_kind":"","external_ref":null,"hook_bead":"","id":"Ghostly-2he","is_template":0,"issue_type":"bug","last_activity":null,"metadata":"{}","mol_type":"","notes":"","original_size":null,"owner":"gabkolistiak@gmail.com","payload":"","pinned":0,"priority":2,"quality_score":null,"rig":"","role_bead":"","role_type":"","sender":"","source_repo":"","source_system":"","spec_id":"","status":"open","target":"","timeout_ns":0,"title":"Auto-focus text editor when popover reopens","updated_at":"2026-03-08T11:32:25Z","waiters":"","wisp_type":"","work_type":""} +{"acceptance_criteria":"","actor":"","agent_state":"","assignee":null,"await_id":"","await_type":"","close_reason":"","closed_at":null,"closed_by_session":"","compacted_at":null,"compacted_at_commit":null,"compaction_level":0,"content_hash":"857fe92127582808e7c82b51a536f27b4af3a153d0a9467852b31b45e0959af6","created_at":"2026-03-04T15:17:24Z","created_by":"Gabko14","crystallizes":0,"defer_until":null,"description":"When typing in a list and pressing Enter, autoContinueList() correctly inserts a new bullet point (• ) on the next line, but the cursor is positioned at the start of the line (before the bullet) instead of after the '• ' prefix. The cursor should be placed right after the bullet and space so the user can continue typing immediately.","design":"","due_at":null,"ephemeral":0,"estimated_minutes":null,"event_kind":"","external_ref":null,"hook_bead":"","id":"Ghostly-7yl","is_template":0,"issue_type":"bug","last_activity":null,"metadata":"{}","mol_type":"","notes":"","original_size":null,"owner":"gabkolistiak@gmail.com","payload":"","pinned":0,"priority":2,"quality_score":null,"rig":"","role_bead":"","role_type":"","sender":"","source_repo":"","source_system":"","spec_id":"","status":"open","target":"","timeout_ns":0,"title":"Fix cursor position after auto-continue list on Enter","updated_at":"2026-03-04T15:17:24Z","waiters":"","wisp_type":"","work_type":""} diff --git a/.beads/backup/labels.jsonl b/.beads/backup/labels.jsonl new file mode 100644 index 0000000..e69de29 diff --git a/.beads/dolt-monitor.pid b/.beads/dolt-monitor.pid new file mode 100644 index 0000000..0eb2e22 --- /dev/null +++ b/.beads/dolt-monitor.pid @@ -0,0 +1 @@ +79528 \ No newline at end of file diff --git a/.beads/dolt-server.activity b/.beads/dolt-server.activity new file mode 100644 index 0000000..8c1b366 --- /dev/null +++ b/.beads/dolt-server.activity @@ -0,0 +1 @@ +1772637437 \ No newline at end of file diff --git a/.beads/dolt-server.port b/.beads/dolt-server.port new file mode 100644 index 0000000..e09768a --- /dev/null +++ b/.beads/dolt-server.port @@ -0,0 +1 @@ +14049 \ No newline at end of file diff --git a/.beads/hooks/post-checkout b/.beads/hooks/post-checkout new file mode 100755 index 0000000..206fe24 --- /dev/null +++ b/.beads/hooks/post-checkout @@ -0,0 +1,9 @@ +#!/usr/bin/env sh +# --- BEGIN BEADS INTEGRATION v0.57.0 --- +# This section is managed by beads. Do not remove these markers. +if command -v bd >/dev/null 2>&1; then + export BD_GIT_HOOK=1 + bd hooks run post-checkout "$@" + _bd_exit=$?; if [ $_bd_exit -ne 0 ]; then exit $_bd_exit; fi +fi +# --- END BEADS INTEGRATION --- diff --git a/.beads/hooks/post-merge b/.beads/hooks/post-merge new file mode 100755 index 0000000..73fc60c --- /dev/null +++ b/.beads/hooks/post-merge @@ -0,0 +1,9 @@ +#!/usr/bin/env sh +# --- BEGIN BEADS INTEGRATION v0.57.0 --- +# This section is managed by beads. Do not remove these markers. +if command -v bd >/dev/null 2>&1; then + export BD_GIT_HOOK=1 + bd hooks run post-merge "$@" + _bd_exit=$?; if [ $_bd_exit -ne 0 ]; then exit $_bd_exit; fi +fi +# --- END BEADS INTEGRATION --- diff --git a/.beads/hooks/pre-commit b/.beads/hooks/pre-commit new file mode 100755 index 0000000..90dc813 --- /dev/null +++ b/.beads/hooks/pre-commit @@ -0,0 +1,9 @@ +#!/usr/bin/env sh +# --- BEGIN BEADS INTEGRATION v0.57.0 --- +# This section is managed by beads. Do not remove these markers. +if command -v bd >/dev/null 2>&1; then + export BD_GIT_HOOK=1 + bd hooks run pre-commit "$@" + _bd_exit=$?; if [ $_bd_exit -ne 0 ]; then exit $_bd_exit; fi +fi +# --- END BEADS INTEGRATION --- diff --git a/.beads/hooks/pre-push b/.beads/hooks/pre-push new file mode 100755 index 0000000..7c3dd9b --- /dev/null +++ b/.beads/hooks/pre-push @@ -0,0 +1,9 @@ +#!/usr/bin/env sh +# --- BEGIN BEADS INTEGRATION v0.57.0 --- +# This section is managed by beads. Do not remove these markers. +if command -v bd >/dev/null 2>&1; then + export BD_GIT_HOOK=1 + bd hooks run pre-push "$@" + _bd_exit=$?; if [ $_bd_exit -ne 0 ]; then exit $_bd_exit; fi +fi +# --- END BEADS INTEGRATION --- diff --git a/.beads/hooks/prepare-commit-msg b/.beads/hooks/prepare-commit-msg new file mode 100755 index 0000000..a9023a4 --- /dev/null +++ b/.beads/hooks/prepare-commit-msg @@ -0,0 +1,9 @@ +#!/usr/bin/env sh +# --- BEGIN BEADS INTEGRATION v0.57.0 --- +# This section is managed by beads. Do not remove these markers. +if command -v bd >/dev/null 2>&1; then + export BD_GIT_HOOK=1 + bd hooks run prepare-commit-msg "$@" + _bd_exit=$?; if [ $_bd_exit -ne 0 ]; then exit $_bd_exit; fi +fi +# --- END BEADS INTEGRATION --- diff --git a/.beads/metadata.json b/.beads/metadata.json index c787975..81a698f 100644 --- a/.beads/metadata.json +++ b/.beads/metadata.json @@ -1,4 +1,6 @@ { - "database": "beads.db", - "jsonl_export": "issues.jsonl" + "database": "dolt", + "backend": "dolt", + "dolt_mode": "server", + "dolt_database": "Ghostly" } \ No newline at end of file diff --git a/.gitignore b/.gitignore index d82f835..0b5b0b6 100644 --- a/.gitignore +++ b/.gitignore @@ -20,3 +20,7 @@ gha-creds-*.json # Xcode .derivedData/ xcuserdata/ + +# Dolt database files (added by bd init) +.dolt/ +*.db diff --git a/AGENTS.md b/AGENTS.md index bd81b18..6253470 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -105,3 +105,90 @@ git push # Push to remote - Always `br sync --flush-only` + commit before ending session + + +## Issue Tracking with bd (beads) + +**IMPORTANT**: This project uses **bd (beads)** for ALL issue tracking. Do NOT use markdown TODOs, task lists, or other tracking methods. + +### Why bd? + +- Dependency-aware: Track blockers and relationships between issues +- Git-friendly: Dolt-powered version control with native sync +- Agent-optimized: JSON output, ready work detection, discovered-from links +- Prevents duplicate tracking systems and confusion + +### Quick Start + +**Check for ready work:** + +```bash +bd ready --json +``` + +**Create new issues:** + +```bash +bd create "Issue title" --description="Detailed context" -t bug|feature|task -p 0-4 --json +bd create "Issue title" --description="What this issue is about" -p 1 --deps discovered-from:bd-123 --json +``` + +**Claim and update:** + +```bash +bd update --claim --json +bd update bd-42 --priority 1 --json +``` + +**Complete work:** + +```bash +bd close bd-42 --reason "Completed" --json +``` + +### Issue Types + +- `bug` - Something broken +- `feature` - New functionality +- `task` - Work item (tests, docs, refactoring) +- `epic` - Large feature with subtasks +- `chore` - Maintenance (dependencies, tooling) + +### Priorities + +- `0` - Critical (security, data loss, broken builds) +- `1` - High (major features, important bugs) +- `2` - Medium (default, nice-to-have) +- `3` - Low (polish, optimization) +- `4` - Backlog (future ideas) + +### Workflow for AI Agents + +1. **Check ready work**: `bd ready` shows unblocked issues +2. **Claim your task atomically**: `bd update --claim` +3. **Work on it**: Implement, test, document +4. **Discover new work?** Create linked issue: + - `bd create "Found bug" --description="Details about what was found" -p 1 --deps discovered-from:` +5. **Complete**: `bd close --reason "Done"` + +### Auto-Sync + +bd automatically syncs via Dolt: + +- Each write auto-commits to Dolt history +- Use `bd dolt push`/`bd dolt pull` for remote sync +- No manual export/import needed! + +### Important Rules + +- ✅ Use bd for ALL task tracking +- ✅ Always use `--json` flag for programmatic use +- ✅ Link discovered work with `discovered-from` dependencies +- ✅ Check `bd ready` before asking "what should I work on?" +- ❌ Do NOT create markdown TODO lists +- ❌ Do NOT use external issue trackers +- ❌ Do NOT duplicate tracking systems + +For more details, see README.md and docs/QUICKSTART.md. + + diff --git a/Ghostly/Utilities/EditorTextViewConfiguration.swift b/Ghostly/Utilities/EditorTextViewConfiguration.swift index 46b4c38..13b0110 100644 --- a/Ghostly/Utilities/EditorTextViewConfiguration.swift +++ b/Ghostly/Utilities/EditorTextViewConfiguration.swift @@ -78,6 +78,7 @@ enum EditorTextViewConfiguration { textView.textStorage?.setAttributedString(normalizedAttributedString(for: text)) textView.selectedRanges = clampedSelectedRanges(selectedRanges, maxLength: text.utf16.count) textView.typingAttributes = textAttributes() + resetScrollState(for: textView) } @MainActor @@ -107,4 +108,13 @@ enum EditorTextViewConfiguration { return NSValue(range: NSRange(location: location, length: length)) } } + + @MainActor + private static func resetScrollState(for textView: NSTextView) { + guard let scrollView = textView.enclosingScrollView else { return } + + textView.scrollRangeToVisible(NSRange(location: 0, length: 0)) + scrollView.contentView.scroll(to: .zero) + scrollView.reflectScrolledClipView(scrollView.contentView) + } } diff --git a/GhostlyTests/EditorTextViewConfigurationTests.swift b/GhostlyTests/EditorTextViewConfigurationTests.swift index 62fc5b2..45b511e 100644 --- a/GhostlyTests/EditorTextViewConfigurationTests.swift +++ b/GhostlyTests/EditorTextViewConfigurationTests.swift @@ -60,4 +60,27 @@ struct EditorTextViewConfigurationTests { let attributes = textView.textStorage?.attributes(at: 0, effectiveRange: nil) #expect((attributes?[.kern] as? CGFloat) == EditorTextViewConfiguration.tracking) } + + @Test("Updating text resets the reused scroll view to the top") + func updatingTextResetsScrollPosition() { + let scrollView = EditorTextViewConfiguration.makeScrollView() + scrollView.frame = NSRect(x: 0, y: 0, width: 300, height: 120) + + let textView = scrollView.documentView as? NSTextView + #expect(textView != nil) + + if let textView { + let longText = Array(repeating: "A long enough line to force scrolling.", count: 60) + .joined(separator: "\n") + EditorTextViewConfiguration.updateText(longText, in: textView) + textView.layoutManager?.ensureLayout(for: textView.textContainer!) + textView.scrollRangeToVisible(NSRange(location: longText.utf16.count, length: 0)) + scrollView.reflectScrolledClipView(scrollView.contentView) + #expect(scrollView.contentView.bounds.origin.y > 0) + + EditorTextViewConfiguration.updateText("", in: textView) + } + + #expect(scrollView.contentView.bounds.origin == .zero) + } }