Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions .github/workflows/cloudflare_pages.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ name: Deploy to Cloudflare Pages
on:
pull_request:
push:
branches:
- main
branches:
- main
jobs:
deploy:
runs-on: ubuntu-latest
Expand Down Expand Up @@ -47,4 +47,4 @@ jobs:
Preview URL: ${{ steps.deploy.outputs.pages-deployment-alias-url }}

Automated deployment preview for the PR in the Cloudflare Pages.
emojis: 'rocket, eyes, +1, -1'
emojis: "rocket, eyes, +1, -1"
2 changes: 1 addition & 1 deletion .github/workflows/docker.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ on:
branches:
- main
tags:
- 'v*.*.*'
- "v*.*.*"

env:
IMAGE_NAME: mattfly/obsidian
Expand Down
10 changes: 5 additions & 5 deletions .github/workflows/publish.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ on:
workflow_dispatch:
inputs:
google-playstore-version:
description: 'Optional: Override Google Play version code'
description: "Optional: Override Google Play version code"
required: false
type: string

Expand Down Expand Up @@ -357,8 +357,8 @@ jobs:
with:
workload_identity_provider: ${{ secrets.GOOGLE_WIF_PROVIDER }}
service_account: ${{ secrets.GOOGLE_WIF_SERVICE_ACCOUNT }}
token_format: 'access_token'
access_token_scopes: 'https://www.googleapis.com/auth/androidpublisher'
token_format: "access_token"
access_token_scopes: "https://www.googleapis.com/auth/androidpublisher"

- name: Setup Java
uses: actions/setup-java@v5
Expand Down Expand Up @@ -480,8 +480,8 @@ jobs:
with:
workload_identity_provider: ${{ secrets.GOOGLE_WIF_PROVIDER }}
service_account: ${{ secrets.GOOGLE_WIF_SERVICE_ACCOUNT }}
token_format: 'access_token'
access_token_scopes: 'https://www.googleapis.com/auth/androidpublisher'
token_format: "access_token"
access_token_scopes: "https://www.googleapis.com/auth/androidpublisher"

- name: Download signed AAB artifact
uses: actions/download-artifact@v8
Expand Down
129 changes: 97 additions & 32 deletions .github/workflows/workflow.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,31 +3,45 @@ name: Lint and Tests
on:
push:

# Every lint/format job is a thin wrapper around `lefthook run pre-commit`
# so lefthook.yml stays the single source of truth for what runs locally and in CI.

jobs:
biome:
name: Lint and Formating
name: Biome (lint & format)
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v6
- name: Setup Biome
uses: biomejs/setup-biome@v2
- name: Setup Node
uses: actions/setup-node@v4
with:
version: latest
- name: Run Biome
run: biome ci .
node-version: 22
cache: npm
- name: Install deps
run: npm ci
- name: Run lefthook check
run: npx lefthook run pre-commit --commands check --all-files
- name: Verify no formatting drift
run: git diff --exit-code

build:
name: Build and Test
timeout-minutes: 10
docs:
name: Docs (prettier & markdownlint)
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v6
- name: Dependencies install
run: npm ci
- name: Test
run: npm run test
- name: Checkout
uses: actions/checkout@v6
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: 22
cache: npm
- name: Install deps
run: npm ci
- name: Run lefthook docs hooks
run: npx lefthook run pre-commit --commands docs-format,docs-lint --all-files
- name: Verify no formatting drift
run: git diff --exit-code

nix-linux:
name: Nix package (Linux x86_64)
Expand All @@ -47,26 +61,22 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Dependencies install
uses: actions/checkout@v6
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: 22
cache: npm
- name: Install deps
run: npm ci
- name: Verify catalog is up to date
# Fails if there are t`...` strings in source that haven't been extracted
# into en/messages.po yet. Fix: npm run i18n:extract && npm run i18n:compile
# and commit the updated src/locales/ files.
run: |
npm run i18n:extract
git diff --exit-code src/locales/en/messages.po || \
(echo "::error::Untranslated strings detected in source. Run 'npm run i18n:extract && npm run i18n:compile' and commit the updated src/locales/ files." && exit 1)
- name: Verify catalogs are compiled
# Fails if .mjs files are missing or stale relative to .po files.
run: |
npm run i18n:compile
git diff --exit-code src/locales/ || \
(echo "::error::Compiled catalogs are out of date. Run 'npm run i18n:compile' and commit the updated src/locales/ files." && exit 1)
- name: Run lefthook i18n hook
# Fails if extract/compile produces a diff against committed catalogs.
# Fix: npm run i18n:extract && npm run i18n:compile, commit src/locales/.
run: npx lefthook run pre-commit --commands i18n --all-files
- name: Verify catalogs are committed
run: git diff --exit-code src/locales/
- name: Report untranslated strings per locale
# Informational only — does not fail the build.
# Shows how many strings still need translation in each non-English locale.
run: |
echo "### Translation coverage" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
Expand All @@ -82,3 +92,58 @@ jobs:
echo "- **${locale}**: ${TRANSLATED}/${TOTAL} strings translated" >> $GITHUB_STEP_SUMMARY
fi
done

rust:
name: Rust fmt & clippy
runs-on: ubuntu-latest
timeout-minutes: 20
steps:
- name: Checkout
uses: actions/checkout@v6
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: 22
cache: npm
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@stable
with:
components: rustfmt, clippy
- name: Cache cargo
uses: Swatinem/rust-cache@v2
with:
workspaces: |
src-tauri
src-tauri/plugins/share-sheet
src-tauri/plugins/ios-keyboard
- name: Install Tauri system deps
run: |
sudo apt-get update
sudo apt-get install -y \
libwebkit2gtk-4.1-dev \
libgtk-3-dev \
libayatana-appindicator3-dev \
librsvg2-dev \
libsoup-3.0-dev \
libjavascriptcoregtk-4.1-dev
- name: Install deps
run: npm ci
- name: Run lefthook rust hooks
run: npx lefthook run pre-commit --commands rust-fmt,rust-clippy --all-files

build:
name: Build and Test
timeout-minutes: 10
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v6
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: 22
cache: npm
- name: Install deps
run: npm ci
- name: Test
run: npm run test
18 changes: 18 additions & 0 deletions .markdownlint.jsonc
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
// Line length — prettier owns wrapping
"MD013": false,
// Duplicate headings — allow same heading text in different sections
"MD024": { "siblings_only": true },
// Ordered list prefix — accept both `1. 1. 1.` and `1. 2. 3.`
"MD029": { "style": "one_or_ordered" },
// Inline HTML — needed for badges, <details>, <br>, <img>
"MD033": false,
// Bare URLs — fine in docs, badges, link lists
"MD034": false,
// Emphasis used as heading — stylistic call, allow
"MD036": false,
// Fenced code language hint — nice but not worth fixing across all docs
"MD040": false,
// First-line H1 — some files lead with badges or autogenerated content
"MD041": false
}
11 changes: 11 additions & 0 deletions .markdownlintignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
node_modules/**
dist/**
build/**
coverage/**
src-tauri/target/**
src-tauri/plugins/*/target/**
src-tauri/plugins/*/.tauri/**
src-tauri/gen/**
.serena/**
.playwright-mcp/**
.husky/**
29 changes: 29 additions & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
node_modules
dist
build
coverage
src-tauri/target
src-tauri/plugins/*/target
src-tauri/plugins/*/.tauri
src-tauri/plugins/*/permissions/autogenerated
src-tauri/gen
.serena
.playwright-mcp
.husky

# Biome owns these
*.js
*.cjs
*.mjs
*.ts
*.tsx
*.jsx
*.json
*.jsonc
*.css
*.scss
*.html

# Lingui generated catalogs
src/locales/**/*.po
src/locales/**/*.mjs
15 changes: 15 additions & 0 deletions .prettierrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"printWidth": 100,
"tabWidth": 2,
"useTabs": false,
"endOfLine": "lf",
"proseWrap": "preserve",
"overrides": [
{
"files": ["*.yml", "*.yaml"],
"options": {
"singleQuote": false
}
}
]
}
32 changes: 19 additions & 13 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ npm run format; npm run fix:unsafe; npm run test; npm run build
- **`nix develop`** — full dev environment (Node 22 + Tauri Linux deps + rustup). Linux only (`x86_64`/`aarch64`).
- **`nix build .#obsidianirc`** — produces `result/bin/ObsidianIRC`. Bump `npmDeps` in [nix/obsidianirc.nix](nix/obsidianirc.nix) when `package-lock.json` changes.
- Details: [BUILD.md — Nix (flake)](BUILD.md#nix-flake)

---

## Project Layout
Expand Down Expand Up @@ -79,7 +80,10 @@ src-tauri/ # Tauri config, Rust backend, plugins (Swift share-s
which dispatches via `IRC_DISPATCH`:

```ts
const IRC_DISPATCH: Record<string, (ctx: IRCClientContext, serverId: string, msg: ParsedMessage) => void> = {
const IRC_DISPATCH: Record<
string,
(ctx: IRCClientContext, serverId: string, msg: ParsedMessage) => void
> = {
PRIVMSG: handlePrivmsg,
JOIN: handleJoin,
"332": handleRplTopic,
Expand All @@ -104,7 +108,9 @@ Each handler file subscribes to `ircClient` events and updates the Zustand store
// Pattern in every src/store/handlers/*.ts
export function registerXxxHandlers(store: StoreApi<AppState>) {
ircClient.on("EVENT", (payload) => {
store.setState((state) => ({ /* return Partial<AppState> — no mutation */ }));
store.setState((state) => ({
/* return Partial<AppState> — no mutation */
}));
});
}
```
Expand Down Expand Up @@ -237,27 +243,27 @@ All user-visible text **must** be wrapped with LinguiJS macros so it can be tran

### Which tool to use


| String location | Macro | Import |
| --------------------------------------------------------- | ---------------------------------- | ------------------------------------------------------------------------------------------------- |
| JSX text children (`<button>`, `<span>`, `<p>`, headings) | `<Trans>…</Trans>` | `import { Trans } from "@lingui/macro"` |
| JSX props: `placeholder=`, `aria-label=`, `title=` | `t`…`` via `useLingui` | `import { useLingui } from "@lingui/macro"` then `const { t } = useLingui()` inside the component |
| Simple `t` outside JSX (inside a render function) | `t`…`` | `import { t } from "@lingui/macro"` |
| Variables/interpolation | `t`Hello ${name}`` | same — placeholders become `{0}` in the PO file |
| Non-React `.ts` files (store handlers, event callbacks) | `t`…`` | `import { t } from "@lingui/macro"` — safe inside callbacks that fire after `i18n.activate()` |
| JSX props: `placeholder=`, `aria-label=`, `title=` | `` t`…` `` via `useLingui` | `import { useLingui } from "@lingui/macro"` then `const { t } = useLingui()` inside the component |
| Simple `t` outside JSX (inside a render function) | `` t`…` `` | `import { t } from "@lingui/macro"` |
| Variables/interpolation | `` t`Hello ${name}` `` | same — placeholders become `{0}` in the PO file |
| Non-React `.ts` files (store handlers, event callbacks) | `` t`…` `` | `import { t } from "@lingui/macro"` — safe inside callbacks that fire after `i18n.activate()` |
| Module-level constants | **Do not use** `t` at module scope | `t` evaluates before `i18n.activate()` runs. Move the string inside the function body. |


### Correct patterns

```tsx
// JSX children
<button><Trans>Save</Trans></button>
<button>
<Trans>Save</Trans>
</button>;

// Props with interpolation (requires useLingui inside the component)
import { useLingui } from "@lingui/macro";
const { t } = useLingui();
<input placeholder={t`Message #${channelName}`} />
<input placeholder={t`Message #${channelName}`} />;

// Simple t tag inside render
import { t } from "@lingui/macro";
Expand Down Expand Up @@ -304,8 +310,8 @@ Rules:
Write the complete translated file back.
```

3. Run `npm run i18n:compile` to regenerate `.mjs` files.
4. Commit `src/locales/`.
1. Run `npm run i18n:compile` to regenerate `.mjs` files.
2. Commit `src/locales/`.

### Adding a new locale

Expand All @@ -326,4 +332,4 @@ The `i18n` job in `.github/workflows/workflow.yaml`:

### Tests

`@lingui/react` is mocked in tests via the alias in `vite.config.ts` → `tests/mocks/lingui-react.ts`. The mock handles `I18nProvider`, `Trans` (including `values` and `components` interpolation), and `useLingui()`. When you add a new component with lingui macros, its tests work without any wrapper changes.
`@lingui/react` is mocked in tests via the alias in `vite.config.ts` → `tests/mocks/lingui-react.ts`. The mock handles `I18nProvider`, `Trans` (including `values` and `components` interpolation), and `useLingui()`. When you add a new component with lingui macros, its tests work without any wrapper changes.
Loading
Loading