Welcome, and thank you for considering a contribution to Granit -- a terminal-based knowledge manager written in Go with Bubble Tea. Whether you are fixing a typo, reporting a bug, proposing a new theme, writing a plugin, or building a major feature, all contributions are welcome and appreciated.
This guide will help you get oriented quickly.
- Code of Conduct
- Getting Started
- Development Setup
- How to Contribute
- Code Style
- Pull Request Process
- Architecture Quick Reference
- Testing
- License
This project is maintained by a small team and a growing community of contributors. We ask that everyone:
- Be respectful. Treat others the way you would want to be treated. Disagreements are fine; personal attacks are not.
- Be constructive. When reviewing code or discussing ideas, focus on the work, not the person. Offer suggestions, not just criticism.
- Be inclusive. We welcome contributors of all experience levels and backgrounds. If someone is new, help them learn rather than dismissing their effort.
- Assume good intent. Most misunderstandings are exactly that -- misunderstandings. Ask for clarification before assuming the worst.
Unacceptable behavior (harassment, trolling, spam) will result in removal from the project. If you experience or witness such behavior, please reach out to the maintainers directly.
-
Fork the repository on GitHub: github.com/artaeon/granit
-
Clone your fork:
git clone https://github.com/<your-username>/granit.git cd granit
-
Build the project:
go build ./cmd/granit/
-
Verify that everything passes:
go vet ./... go test ./... -
Run Granit:
./granit ~/your-vault # open a specific vault ./granit # open the vault selector
If your Go toolchain lives at a custom path (e.g. ~/go-sdk/go/bin/go),
substitute that for go in the commands above.
- Go 1.23 or later
- Git
- A terminal emulator (any modern terminal will do)
cmd/granit/
main.go CLI entry point (open, scan, daily, version, help)
internal/config/
config.go JSON config (global ~/.config/granit/ + per-vault .granit.json)
vaults.go Vault list persistence
import.go Obsidian config importer
internal/vault/
vault.go Vault scanning and note storage
parser.go Markdown, frontmatter, and wikilink parser
index.go Backlink and link index
internal/tui/
app.go Main Bubble Tea model (~2150 lines)
editor.go Text editor with multi-cursor support (~1240 lines)
renderer.go Markdown rendering (view mode)
sidebar.go File tree sidebar with fuzzy search
filetree.go Collapsible folder hierarchy
styles.go Package-level color vars and style definitions
themes.go Theme structs (38 built-in themes)
command.go Command palette (Ctrl+X)
settings.go Settings overlay (Ctrl+,)
bots.go AI bots: Ollama + OpenAI + local fallback
plugins.go Plugin system and manager overlay
vim.go Vim modal editing
... One file per overlay/feature
All TUI components live in internal/tui/ and follow the Bubble Tea
Model / Update / View pattern.
| Directory | Purpose |
|---|---|
cmd/granit/ |
CLI entry point and subcommands |
internal/config/ |
Configuration loading, saving, and importing |
internal/vault/ |
Vault scanning, parsing, and indexing |
internal/tui/ |
All TUI components, overlays, and themes |
Found a bug? Open an issue with:
- Steps to reproduce -- Numbered steps someone else can follow to trigger the issue.
- Expected behavior -- What you expected to happen.
- Actual behavior -- What actually happened (include error messages or garbled output if applicable).
- Environment -- OS, terminal emulator, Granit version (
granit version), and Go version (go version). - Screenshots -- If the issue is visual, a screenshot or recording helps enormously.
Have an idea? Open a feature request. Focus on the use case rather than just the feature itself. Explain:
- What problem are you trying to solve?
- How do you currently work around it (if at all)?
- Why would this benefit other Granit users?
A well-explained use case helps maintainers evaluate and prioritize the request, even if the final implementation looks different from what you originally proposed.
-
Fork the repository and create a feature branch from
main:git checkout -b my-feature
-
Make your changes in focused, logical commits.
-
Test your changes (see Testing).
-
Push to your fork:
git push origin my-feature
-
Open a pull request against
main.
Documentation fixes -- typos, clarifications, better examples -- are always welcome. No issue required; just open a PR.
Themes are defined in internal/tui/themes.go as Theme structs in the
builtinThemes map. Each theme has 16 color roles:
| Role | Purpose |
|---|---|
Primary |
Primary accent color (selections, highlights) |
Secondary |
Secondary accent |
Accent |
Tertiary accent for emphasis |
Warning |
Warning indicators |
Success |
Success indicators |
Error |
Error indicators |
Info |
Informational highlights |
Text |
Main text color |
Subtext |
Dimmer text (descriptions, hints) |
Dim |
Even dimmer text (disabled items, line numbers) |
Surface2 |
Lightest surface (active borders) |
Surface1 |
Mid surface (borders, separators) |
Surface0 |
Dark surface (inactive panels) |
Base |
Main background |
Mantle |
Slightly darker background (sidebars) |
Crust |
Darkest background (status bar) |
To add a theme:
- Add an entry to
builtinThemesinthemes.gowith all 16 color roles filled in. Use existing themes as a reference. - The theme will automatically appear in the settings panel. No other wiring is required.
- Test your theme across multiple overlays (editor, sidebar, command palette, settings, graph view) to ensure readability and contrast.
Granit supports external plugins. Plugins live in:
~/.config/granit/plugins/<name>/(global)<vault>/.granit/plugins/<name>/(per-vault)
Each plugin has a plugin.json manifest defining its name, description,
version, commands, and hooks (on_save, on_open, on_create, on_delete).
Scripts receive context via environment variables (GRANIT_NOTE_PATH,
GRANIT_NOTE_NAME, GRANIT_VAULT_PATH) and note content via stdin.
See the plugin system documentation or existing plugins for examples.
Granit has a consistent internal style. Before writing new code, read a few
existing files in internal/tui/ to absorb the patterns.
- Value receivers for
UpdateandView-- these return a new copy of the overlay struct, matching the Bubble Tea pattern. - Pointer receivers for helpers --
Open,Close,SetSize,IsActive, and any internal methods that mutate state.
// Value receivers -- Bubble Tea interface
func (t TagBrowser) Update(msg tea.Msg) (TagBrowser, tea.Cmd) { ... }
func (t TagBrowser) View() string { ... }
// Pointer receivers -- helpers
func (t *TagBrowser) Open() { ... }
func (t *TagBrowser) Close() { ... }
func (t *TagBrowser) SetSize(w, h int) { ... }
func (t *TagBrowser) IsActive() bool { ... }- No hardcoded colors outside
styles.goandthemes.go. All color values must come from the package-level style variables (mauve,peach,green,blue,surface1,base, etc.) defined instyles.go. - These variables are overwritten at runtime by
ApplyTheme()when the user switches themes. Hardcoding hex values will break theme support. - The default theme is Catppuccin Mocha.
Use ASCII-only characters for the default icon set. The project provides multiple icon sets (Unicode, Nerd Font, Emoji, ASCII) and the default must remain legible in any terminal.
Keep third-party dependencies minimal. The current set is:
- Bubble Tea -- TUI framework
- Lip Gloss -- styling
- GopherLua -- Lua scripting engine
Do not add new dependencies without discussion in an issue first.
-
Make individual commits per logical change. Each commit should be a self-contained, coherent unit.
-
Use a short imperative summary (under ~72 characters) starting with a capitalized verb:
Add toast notification system for ephemeral messages Fix kanban board column alignment and divider rendering Polish sidebar with accent bar selection and styled search -
Do not use conventional-commit prefixes (
feat:,fix:, etc.) -- the project does not use them.
Always run these commands and ensure they pass:
go vet ./...
go test ./...
go build ./cmd/granit/- Fork the repository and create a feature branch from
main. - Make your changes in focused, logical commits.
- Confirm that
go build ./...,go vet ./..., andgo test ./...all pass. - Open a pull request against
mainwith:- A clear title and description -- explain what the change does and why.
- A link to the related issue, if one exists.
- Screenshots or GIFs for any UI changes. Visual changes are much easier to review with a before/after comparison.
- A note about new dependencies or configuration options, if any were introduced.
- Ensure CI passes (build + vet + test).
- Be responsive to review feedback. Small, focused PRs are easier to review and merge.
Overlays are modal panels rendered on top of the main editor. They share a consistent pattern:
- A struct with
active bool,width int,height intfields. Open()/Close()/IsActive()/SetSize()on pointer receivers.Update()/View()on value receivers.- Wired into
app.go: dispatched inUpdatewhen active, rendered inViewon top of the main layout, and triggered via keybindings or the command palette.
Overlays are checked in priority order in both Update and View -- the
first active overlay wins input focus.
Configuration is layered:
- Global config --
~/.config/granit/config.json(user-wide defaults). - Per-vault config --
<vault>/.granit.json(vault-specific overrides).
Per-vault settings take precedence over global settings.
The AI/bots system supports three backends:
- Ollama -- Local LLM via HTTP (
/api/generate). - OpenAI -- Remote API via HTTP (
/v1/chat/completions). - Local fallback -- Keyword analysis, stopword filtering, pattern matching (no external dependencies).
The active provider is set via the AIProvider config key ("local",
"ollama", or "openai"). All AI calls are dispatched asynchronously via
tea.Cmd to keep the TUI responsive.
Granit adapts its layout based on terminal width:
- Default (width >= 120) -- 3-panel: sidebar + editor + backlinks.
- Writer (width 80--119) -- 2-panel: sidebar + editor.
- Minimal (width < 80) -- Editor only.
go test ./... # run all tests
go test ./internal/vault/ # run tests for a specific package
go vet ./... # static analysisTest files live alongside the code they cover, following Go convention:
internal/vault/vault_test.gointernal/vault/parser_test.gointernal/config/config_test.go
- When adding new logic to the
vaultorconfigpackages, add corresponding test functions. - TUI overlay code is harder to unit-test, but at minimum ensure that:
go build ./...succeeds with no errors.go vet ./...reports no issues.- Manual smoke-testing covers the happy path of your feature.
- Test function names should be descriptive:
TestParseWikiLink,TestFrontmatterExtraction, etc.
Granit is licensed under the MIT License. By submitting a contribution (code, documentation, themes, plugins, or otherwise), you agree that your contribution will be licensed under the same MIT License that covers the project.
Thank you for helping make Granit better.