-
-
Notifications
You must be signed in to change notification settings - Fork 201
docs: add a contributor architecture overview (optional, take what's useful) #540
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. Weβll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
horner
wants to merge
1
commit into
johnste:main
Choose a base branch
from
horner:main
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+332
β0
Open
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,142 @@ | ||
| ## Project Overview | ||
|
|
||
| Finicky is a native macOS default-browser router. **The single source of truth for the | ||
| technical stack, repository layout, and build pipeline is [docs/ARCHITECTURE.md](../docs/ARCHITECTURE.md)** β | ||
| read it first and do not duplicate that information here. | ||
|
|
||
| Project-specific conventions (the rest of this file is generic guidance): | ||
|
|
||
| - **Build/test through `scripts/`**, never ad-hoc commands: `./scripts/install.sh`, | ||
| `./scripts/build.sh`, `./scripts/test.sh`, `./scripts/dev.sh`. Keep these scripts as the | ||
| one place build logic lives. | ||
| - **Use `npm`** for the TypeScript packages (`config-api`, `finicky-ui`) β the committed | ||
| `package-lock.json` is authoritative, despite a stray `packageManager: pnpm` field. | ||
| - **Go + cgo/Objective-C**: the macOS app lives in `apps/finicky/src`. Native glue comes in | ||
| matched `.go`/`.h`/`.m` sets β change them together. | ||
| - **User config runs in Goja**, not Node. Don't assume Node/browser globals are available. | ||
| - When the stack or build flow changes, update `docs/ARCHITECTURE.md` (one place) rather | ||
| than restating details elsewhere. | ||
|
|
||
| ## Code Quality Principles | ||
|
|
||
| <!-- https://github.com/mieweb/template-mieweb-opensource/blob/main/.github/copilot-instructions.md --> | ||
|
|
||
| ### π― DRY (Don't Repeat Yourself) | ||
| - **Never duplicate code**: If you find yourself copying code, extract it into a reusable function | ||
| - **Single source of truth**: Each piece of knowledge should have one authoritative representation | ||
| - **Refactor mercilessly**: When you see duplication, eliminate it immediately | ||
| - **Shared utilities**: Common patterns should be abstracted into utility functions | ||
|
|
||
| ### π KISS (Keep It Simple, Stupid) | ||
| - **Simple solutions**: Prefer the simplest solution that works | ||
| - **Avoid over-engineering**: Don't add complexity for hypothetical future needs | ||
| - **Clear naming**: Functions and variables should be self-documenting | ||
| - **Small functions**: Break down complex functions into smaller, focused ones | ||
| - **Readable code**: Code should be obvious to understand at first glance | ||
|
|
||
| ### π§Ή Folder Philosophy | ||
| - **Clear purpose**: Every folder should have a main thing that anchors its contents. | ||
| - **No junk drawers**: Donβt leave loose files without context or explanation. | ||
| - **Explain relationships**: If itβs not elegantly obvious how files fit together, add a README or note. | ||
| - **Immediate clarity**: Opening a folder should make its organizing principle clear at a glance. | ||
|
|
||
| ### π Refactoring Guidelines | ||
| - **Continuous improvement**: Refactor as you work, not as a separate task | ||
| - **Safe refactoring**: Always run tests before and after refactoring | ||
| - **Incremental changes**: Make small, safe changes rather than large rewrites | ||
| - **Preserve behavior**: Refactoring should not change external behavior | ||
| - **Code reviews**: All refactoring should be reviewed for correctness | ||
|
|
||
| ### β°οΈ Dead Code Management | ||
| - **Immediate removal**: Delete unused code immediately when identified | ||
| - **Historical preservation**: Move significant dead code to `.attic/` directory with context | ||
| - **Documentation**: Include comments explaining why code was moved to attic | ||
| - **Regular cleanup**: Review and clean attic directory periodically | ||
| - **No accumulation**: Don't let dead code accumulate in active codebase | ||
|
|
||
| ### π Testing with MCP Browser | ||
| - Use MCP browser in Playwright if available to test functionality | ||
| - **Never close the browser** after running MCP browser commands unless explicitly asked | ||
| - Let the user interact with the browser after navigation or testing | ||
| - Only use `browser_close` when the user specifically requests it | ||
|
|
||
| ## HTML & CSS Guidelines | ||
| - **Semantic Naming**: Every `<div>` and other structural element must use a meaningful, semantic class name that clearly indicates its purpose or role within the layout. | ||
| - **CSS Simplicity**: Styles should avoid global resets or overrides that affect unrelated components or default browser behavior. Keep changes scoped and minimal. | ||
| - **SASS-First Approach**: All styles should be written in SASS (SCSS) whenever possible. Each component should have its own dedicated SASS file to promote modularity and maintainability. | ||
|
|
||
| ## Accessibility (ARIA Labeling) | ||
|
|
||
| ### π― Interactive Elements | ||
| - **All interactive elements** (buttons, links, forms, dialogs) must include appropriate ARIA roles and labels | ||
| - **Use ARIA attributes**: Implement aria-label, aria-labelledby, and aria-describedby to provide clear, descriptive information for screen readers | ||
| - **Semantic HTML**: Use semantic HTML wherever possible to enhance accessibility | ||
|
|
||
| ### π’ Dynamic Content | ||
| - **Announce updates**: Ensure all dynamic content updates (modals, alerts, notifications) are announced to assistive technologies using aria-live regions | ||
| - **Maintain tab order**: Maintain logical tab order and keyboard navigation for all features | ||
| - **Visible focus**: Provide visible focus indicators for all interactive elements | ||
|
|
||
| ## Internationalization (I18N) | ||
|
|
||
| ### π Text and Language Support | ||
| - **Externalize text**: All user-facing text must be externalized for translation | ||
| - **Multiple languages**: Support multiple languages, including right-to-left (RTL) languages such as Arabic and Hebrew | ||
| - **Language selector**: Provide a language selector for users to choose their preferred language | ||
|
|
||
| ### π Localization | ||
| - **Format localization**: Ensure date, time, number, and currency formats are localized based on user settings | ||
| - **UI compatibility**: Test UI layouts for text expansion and RTL compatibility | ||
| - **Unicode support**: Use Unicode throughout to support international character sets | ||
|
|
||
| ## Documentation Preferences | ||
|
|
||
| ### Diagrams and Visual Documentation | ||
| - **Always use Mermaid diagrams** instead of ASCII art for workflow diagrams, architecture diagrams, and flowcharts | ||
| - **Use memorable names** instead of single letters in diagrams (e.g., `Engine`, `Auth`, `Server` instead of `A`, `B`, `C`) | ||
| - Use appropriate Mermaid diagram types: | ||
| - `graph TB` or `graph LR` for workflow architectures | ||
| - `flowchart TD` for process flows | ||
| - `sequenceDiagram` for API interactions | ||
| - `gitgraph` for branch/release strategies | ||
| - Include styling with `classDef` for better visual hierarchy | ||
| - Add descriptive comments and emojis sparingly for clarity | ||
|
|
||
| ### Documentation Standards | ||
| - Keep documentation DRY (Don't Repeat Yourself) - reference other docs instead of duplicating | ||
| - Use clear cross-references between related documentation files | ||
| - Update the main architecture document when workflow structure changes | ||
|
|
||
| ## Working with GitHub Actions Workflows | ||
|
|
||
| ### Development Philosophy | ||
| - **Script-first approach**: All workflows should call scripts that can be run locally | ||
| - **Local development parity**: Developers should be able to run the exact same commands locally as CI runs | ||
| - **Simple workflows**: GitHub Actions should be thin wrappers around scripts, not contain complex logic | ||
| - **Easy debugging**: When CI fails, developers can reproduce the issue locally by running the same script | ||
|
|
||
| ## Quick Reference | ||
|
|
||
| ### πͺΆ All Changes should be considered for Pull Request Philosophy | ||
|
|
||
| * **Smallest viable change**: Always make the smallest change that fully solves the problem. | ||
| * **Fewest files first**: Start with the minimal number of files required. | ||
| * **No sweeping edits**: Broad refactors or multi-module changes must be split or proposed as new components. | ||
| * **Isolated improvements**: If a change grows complex, extract it into a new function, module, or component instead of modifying multiple areas. | ||
| * **Direct requests only**: Large refactors or architectural shifts should only occur when explicitly requested. | ||
| * **Update the docs**: Keep documentation in sync with the change in the same PR. When behavior, stack, or build flow changes, update the relevant docs (e.g. [docs/ARCHITECTURE.md](../docs/ARCHITECTURE.md)) rather than leaving them stale. | ||
| * **Show UI changes**: Any change to the `finicky-ui` interface must update affected docs/screenshots, and the PR description must include before/after screenshots (or a short clip) of the UI. | ||
|
|
||
| ### Code Quality Checklist | ||
| - [ ] **DRY**: No code duplication - extracted reusable functions? | ||
| - [ ] **KISS**: Simplest solution that works? | ||
| - [ ] **Minimal Changes**: Smallest viable change made for PR? | ||
| - [ ] **Naming**: Self-documenting function/variable names? | ||
| - [ ] **Size**: Functions small and focused? | ||
| - [ ] **Dead Code**: Removed or archived appropriately? | ||
| - [ ] **Accessibility**: ARIA labels and semantic HTML implemented? | ||
| - [ ] **I18N**: User-facing text externalized for translation? | ||
| - [ ] **Docs**: Documentation updated to match the change? | ||
| - [ ] **Screenshots**: UI changes include before/after screenshots in the PR? | ||
| - [ ] **Lint**: Run linter if appropriate | ||
| - [ ] **Test**: Run tests | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,188 @@ | ||
| # Architecture & Technical Stack | ||
|
|
||
| This document gives contributors a fast, high-level map of how Finicky is built so | ||
| you can find your way around the repository quickly. For step-by-step build | ||
| instructions, see the wiki page | ||
| [Building Finicky from source](https://github.com/johnste/finicky/wiki/Building-Finicky-from-source). | ||
|
|
||
| ## What Finicky is | ||
|
|
||
| Finicky is a native **macOS** application that registers itself as the system | ||
| default browser. When you click a link anywhere on macOS, the OS hands the URL to | ||
| Finicky, which evaluates your JavaScript/TypeScript configuration and opens the | ||
| "right" browser (optionally rewriting the URL first). | ||
|
|
||
| The whole thing ships as a single self-contained `.app` β there is no Node.js | ||
| runtime requirement on the user's machine. User config is executed inside an | ||
| embedded JavaScript engine compiled into the Go binary. | ||
|
|
||
| ## Repository layout | ||
|
|
||
| ``` | ||
| apps/ | ||
| finicky/ The macOS application (Go + Objective-C, cgo) | ||
| browser-addon/ Chrome/Firefox extension ("Open with Finicky") | ||
| packages/ | ||
| config-api/ TypeScript config API + type definitions (bundled into the app) | ||
| finicky-ui/ Svelte web UI shown in the app window (bundled into the app) | ||
| example-config/ Example user configs (example.js / example.ts) | ||
| scripts/ Build / dev / test / release shell scripts | ||
| testdata/ Sample config.js and rules.json for local dev scenarios | ||
| docs/ This document | ||
| ``` | ||
|
|
||
| ## Languages & tooling at a glance | ||
|
|
||
| | Area | Stack | | ||
| | ------------------- | --------------------------------------------------------------------- | | ||
| | macOS app core | **Go 1.24** with **cgo** bridging to **Objective-C** (Cocoa/AppKit) | | ||
| | JS config execution | **Goja** (pure-Go JS engine) + **goja-babel** + **esbuild** (Go pkg) | | ||
| | Config API | **TypeScript**, bundled with **esbuild**, validated with **Zod** | | ||
| | App window UI | **Svelte 5** + **Vite 6** + **TypeScript**, routed via `svelte-routing` | | ||
| | Browser extension | **JavaScript**, MV3 (Chrome) / MV2-style (Firefox) manifests | | ||
| | Tests | **Go `testing`** (app core), **Vitest** (config-api) | | ||
| | Build orchestration | **Bash** scripts in `scripts/` | | ||
| | CI | **GitHub Actions** (`.github/workflows/macos-build.yml`) | | ||
| | Packaging | Universal binary via `lipo`; signing/notarization via **gon** | | ||
|
|
||
| ## The macOS app β `apps/finicky/src` | ||
|
|
||
| The app is written in Go, with the macOS-specific UI/system glue written in | ||
| Objective-C and called through cgo. Files come in matched sets, e.g. | ||
| [browser.go](../apps/finicky/src/browser.go) / | ||
| [browser.h](../apps/finicky/src/browser.h) / | ||
| [browser.m](../apps/finicky/src/browser.m), where the `.go` file declares the cgo | ||
| bridge and the `.m` file holds the native Cocoa implementation. | ||
|
|
||
| Entry point: [main.go](../apps/finicky/src/main.go) (paired with `main.h`/`main.m`). | ||
|
|
||
| ### Go dependencies ([go.mod](../apps/finicky/src/go.mod)) | ||
|
|
||
| - `github.com/dop251/goja` β JavaScript engine (runs user config without Node). | ||
| - `github.com/jvatic/goja-babel` β Babel inside Goja, so modern JS/TS syntax works. | ||
| - `github.com/evanw/esbuild` β used as a Go library to transform/bundle config. | ||
| - `github.com/fsnotify/fsnotify` β watch the user config file for changes. | ||
| - `github.com/Masterminds/semver` β version comparisons (e.g. update checks). | ||
| - `al.essio.dev/pkg/shellescape` β safe shell argument escaping when launching browsers. | ||
|
|
||
| ### Subpackages | ||
|
|
||
| | Package | Responsibility | | ||
| | ---------- | ------------------------------------------------------------------------------ | | ||
| | `config` | Loads and runs user config. [vm.go](../apps/finicky/src/config/vm.go) drives the Goja VM; also `cache.go`, `configfiles.go`, `console.go`. | | ||
| | `resolver` | Core URL resolution pipeline β given a URL, decide the browser. (`resolver.go`) | | ||
| | `rules` | Matching/rewrite rule evaluation. (`rules.go`) | | ||
| | `browser` | Browser detection and launching. `browsers.json`, `detect.go`, `launcher.go`. | | ||
| | `shorturl` | Expands short/redirect URLs. `resolver.go`, `shortener_domains.json`. | | ||
| | `window` | Native app window (Go + Obj-C). `window.go`/`.h`/`.m`. | | ||
| | `util` | Directory helpers and macOS app info via Obj-C. `directories.go`, `info.*`. | | ||
| | `logger` | Application logging. `logger.go`. | | ||
| | `version` | Version metadata; `commitHash`/`buildDate`/`apiHost` are injected at build time via `-ldflags`. | | ||
| | `assets` | Embeds the built UI + config API into the binary (see below). | | ||
|
|
||
| ### How web assets get into the binary | ||
|
|
||
| [assets/assets.go](../apps/finicky/src/assets/assets.go) uses Go's `//go:embed | ||
| templates/*` to compile the built Svelte UI directly into the executable. The | ||
| build script copies artifacts into `apps/finicky/src/assets/` before `go build`: | ||
|
|
||
| - `packages/config-api/dist/finickyConfigAPI.js` β `assets/finickyConfigAPI.js` | ||
| (the JS API surface that user configs run against, loaded into Goja). | ||
| - `packages/finicky-ui/dist/*` β `assets/templates/` (the embedded UI). | ||
|
|
||
| ## Config API β `packages/config-api` | ||
|
|
||
| TypeScript package that defines the public configuration API and the generated | ||
| `finicky.d.ts` types users get for autocompletion. | ||
|
|
||
| - Build: `esbuild` bundles `src/index.ts` into a single IIFE | ||
| (`dist/finickyConfigAPI.js`, global `finickyConfigAPI`, target `es2015`) so it | ||
| can be evaluated inside Goja. | ||
| - Validation: **Zod** (`configSchema.ts`) + `zod-validation-error`. Types are | ||
| generated from the Zod schema via `@duplojs/zod-to-typescript` | ||
| (`scripts/generate-typedefs.ts`). | ||
| - Tests: **Vitest** (e.g. `config.test.ts`, `FinickyURL.test.ts`, `wildcard.test.ts`). | ||
|
|
||
| This package is consumed by the Go app at build time (bundled + embedded), not at | ||
| runtime as an npm dependency. | ||
|
|
||
| ## App UI β `packages/finicky-ui` | ||
|
|
||
| The window shown when you launch Finicky manually (start page, rules viewer, log | ||
| viewer, test-URL tool, about). | ||
|
|
||
| - **Svelte 5** components/pages under `src/components` and `src/pages`. | ||
| - **Vite 6** build, **TypeScript**, `svelte-routing` for navigation. | ||
| - `svelte-check` for type checking. | ||
| - Built output (`dist/`) is copied into `assets/templates/` and embedded in the Go | ||
| binary. | ||
|
|
||
| > Note: the package declares `pnpm` as its `packageManager`, but the build/CI | ||
| > scripts currently install and build with **npm** (`package-lock.json` is the | ||
| > lockfile used in CI). | ||
|
|
||
| ## Browser extension β `apps/browser-addon` | ||
|
|
||
| A small WebExtension that adds an "Open with Finicky" action and lets alt-click | ||
| open a link via Finicky. Separate manifests for each browser | ||
| (`manifest-chrome.json`, `manifest-firefox.json`) with `background.js` and | ||
| `contentScript.js`; `build.sh` packages it. | ||
|
|
||
| ## Build pipeline β `scripts/` | ||
|
|
||
| The build is orchestrated by Bash. The key scripts: | ||
|
|
||
| - [install.sh](../scripts/install.sh) β installs npm deps for both TS packages. | ||
| - [build.sh](../scripts/build.sh) β the main pipeline: | ||
| 1. Build `config-api` (esbuild + type generation), copy bundle into the Go assets. | ||
| 2. Build `finicky-ui` (Vite), copy `dist/*` into `assets/templates/`. | ||
| 3. `go build` the app with cgo for the target arch(es), injecting version | ||
| metadata via `-ldflags`. | ||
| 4. Assemble the `.app` bundle and copy `finicky.d.ts` + `Contents/` assets. | ||
| - Modes: `BUILD_UNIVERSAL=1` builds **arm64 + amd64** and merges them with | ||
| `lipo` into a universal binary; `BUILD_TARGET_ARCH` builds a single arch; | ||
| otherwise it does a **local native-arch** build and installs to | ||
| `/Applications/Finicky.app`. | ||
| - [dev.sh](../scripts/dev.sh) β runs the already-built app in different config | ||
| scenarios (no config, JS only, rules.json only, both) without rebuilding, using | ||
| `testdata/`. Supports `FINICKY_MOCK_UPDATE` to preview the update banner. | ||
| - [test.sh](../scripts/test.sh) β runs `go test ./...` for the app core. | ||
| - [watch.sh](../scripts/watch.sh) / [watch-run.sh](../scripts/watch-run.sh) β | ||
| rebuild/run on change during development. | ||
| - [release.sh](../scripts/release.sh) + [gon-config.json](../scripts/gon-config.json) | ||
| β codesigning and notarization via gon. | ||
|
|
||
| ## Continuous integration | ||
|
|
||
| [.github/workflows/macos-build.yml](../.github/workflows/macos-build.yml) runs on | ||
| `macos-latest`: | ||
|
|
||
| 1. **build-macos** β sets up Node 22 + Go 1.24, runs `install.sh`, then | ||
| `BUILD_UNIVERSAL=1 build.sh`, and uploads `Finicky-universal.tar.gz`. | ||
| 2. **sign-and-notarize** β on `main` push / manual dispatch, signs and notarizes | ||
| the universal app. | ||
|
|
||
| (There is also a legacy `nodejs.yml` workflow targeting the old `config-api` | ||
| layout/branches.) | ||
|
|
||
| ## End-to-end flow (clicking a link) | ||
|
|
||
| ```mermaid | ||
| flowchart TD | ||
| macosURL[macOS hands URL to Finicky] --> objcAppLayer[Obj-C app layer .m via cgo] | ||
| objcAppLayer --> goResolver[Go core: resolver] | ||
| goResolver --> configVM[config VM: Goja runs user JS/TS] | ||
| configVM --> configApi[config-api bundle + Zod validation] | ||
| goResolver --> rulesEngine[rules + shorturl matching/rewrite] | ||
| rulesEngine --> browserLauncher[browser: detect + launch chosen browser] | ||
| objcAppLayer --> finickyUI[finicky-ui Svelte window for manual launch/troubleshooting] | ||
| ``` | ||
|
|
||
| ## Where to start as a contributor | ||
|
|
||
| - Changing URL matching/rewriting logic β `apps/finicky/src/rules`, | ||
| `apps/finicky/src/resolver`, and the `config-api` schema. | ||
| - Changing the config API surface or types β `packages/config-api/src`. | ||
| - Changing the app window UI β `packages/finicky-ui/src`. | ||
| - Native macOS behavior (window, launching, app info) β the `.m`/`.h` files in | ||
| `apps/finicky/src` and its `window`/`util` packages. |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
π Maintainability & Code Quality | π‘ Minor | β‘ Quick win
π§© Analysis chain
π Script executed:
Repository: johnste/finicky
Length of output: 219
Align the styling guidance with the current CSS setup.
packages/finicky-uiuses plain CSS (src/app.css,src/reset.css), so βAll styles should be written in SASS (SCSS)β is misleading unless Sass support is added first.π€ Prompt for AI Agents