Skip to content
Draft
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/labeler.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ labels:
- label: 'build'
sync: true
matcher:
title: '^(ci|build)(\((?!release)\w+\))?: (?!.*\brelease\b).*'
title: '^(ci|build)(\((?!release)\w+\))?: (?!.*\brelease\b).*'

- label: 'chore'
sync: true
Expand Down Expand Up @@ -46,7 +46,7 @@ labels:
sync: true
matcher:
title: '^test(\(\w+\))?: .*'

checks:
- context: 'Semantic Pull Request'
description:
Expand All @@ -62,4 +62,4 @@ checks:
- build
- release
- chore
- dependency
- dependency
2 changes: 1 addition & 1 deletion .github/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,4 +29,4 @@ changelog:
- chore
- title: Other Changes
labels:
- "*"
- '*'
6 changes: 3 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,12 @@ on:
branches:
- main
paths-ignore:
- "docs/**"
- 'docs/**'
pull_request:
branches:
- main
paths-ignore:
- "docs/**"
- 'docs/**'

permissions: {}

Expand All @@ -32,7 +32,7 @@ jobs:
permissions:
contents: read
with:
directory: "."
directory: '.'

tests:
name: Tests
Expand Down
6 changes: 3 additions & 3 deletions .github/workflows/docs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@ on:
branches:
- main
paths:
- "docs/**"
- 'docs/**'
pull_request:
paths:
- "docs/**"
- 'docs/**'

permissions: {}

Expand All @@ -19,4 +19,4 @@ jobs:
permissions:
contents: read
with:
directory: "docs"
directory: 'docs'
8 changes: 4 additions & 4 deletions .github/workflows/lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,13 @@ on:
directory:
type: string
required: true
description: "The directory to lint"
description: 'The directory to lint'

permissions: {}

jobs:
lint-code:
name: Lint Code [biomejs]
name: Lint Code [vite-plus]
runs-on: ubuntu-latest
permissions:
contents: read
Expand All @@ -28,8 +28,8 @@ jobs:
with:
directory: ${{ inputs.directory }}

- name: Run linter
run: pnpm lint:check
- name: Run lint and format checks
run: pnpm check
working-directory: ${{ inputs.directory }}

lint-i18n:
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ jobs:
permissions:
contents: read
with:
directory: "."
directory: '.'

tests:
name: Tests
Expand Down
5 changes: 4 additions & 1 deletion .github/workflows/renovate.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,13 @@ jobs:
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
persist-credentials: false
sparse-checkout: |
renovate.json
.github

- uses: ./.github/actions/setup-node
with:
run-install: "false"
run-install: 'false'

- name: Install Renovate
run: pnpm install --global renovate
Expand Down
40 changes: 20 additions & 20 deletions .github/workflows/zizmor.yml
Original file line number Diff line number Diff line change
@@ -1,28 +1,28 @@
name: GitHub Actions Security Analysis

on:
push:
branches:
- main
pull_request:
branches:
- "**"
push:
branches:
- main
pull_request:
branches:
- '**'

permissions: {}

jobs:
zizmor:
name: Run zizmor 🌈
runs-on: ubuntu-latest
permissions:
security-events: write # Required for upload-sarif (used by zizmor-action) to upload SARIF files.
contents: read # Only needed for private repos. Needed to clone the repo.
actions: read # Only needed for private repos. Needed for upload-sarif to read workflow run info.
steps:
- name: Checkout repository
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
persist-credentials: false
zizmor:
name: Run zizmor 🌈
runs-on: ubuntu-latest
permissions:
security-events: write # Required for upload-sarif (used by zizmor-action) to upload SARIF files.
contents: read # Only needed for private repos. Needed to clone the repo.
actions: read # Only needed for private repos. Needed for upload-sarif to read workflow run info.
steps:
- name: Checkout repository
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
persist-credentials: false

- name: Run zizmor 🌈
uses: zizmorcore/zizmor-action@5f14fd08f7cf1cb1609c1e344975f152c7ee938d # v0.5.6
- name: Run zizmor 🌈
uses: zizmorcore/zizmor-action@5f14fd08f7cf1cb1609c1e344975f152c7ee938d # v0.5.6
2 changes: 1 addition & 1 deletion .husky/pre-commit
Original file line number Diff line number Diff line change
@@ -1 +1 @@
pnpm dlx lint-staged
pnpm exec vp staged
1 change: 1 addition & 0 deletions .vscode/extensions.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
"GraphQL.vscode-graphql",
"GraphQL.vscode-graphql-syntax",
"lokalise.i18n-ally",
"oxc.oxc-vscode",
"SonarSource.sonarlint-vscode",
"vitest.explorer",
"zizmor.zizmor-vscode"
Expand Down
22 changes: 17 additions & 5 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -1,27 +1,39 @@
{
"editor.codeActionsOnSave": {
"source.fixAll.biome": "explicit"
"source.fixAll.biome": "explicit",
"source.fixAll.oxc": "explicit"
},
"editor.defaultFormatter": "biomejs.biome",
"editor.defaultFormatter": "oxc.oxc-vscode",
"editor.formatOnSave": true,
"oxc.lint.configPath": "./vite.config.ts",
"oxc.fmt.configPath": "./vite.config.ts",
"files.associations": {
"*.css": "tailwindcss"
},
"[astro]": {
"editor.defaultFormatter": "astro-build.astro-vscode"
},
"[json]": {
"editor.defaultFormatter": "biomejs.biome"
"editor.defaultFormatter": "oxc.oxc-vscode"
},
"[typescript]": {
"editor.defaultFormatter": "biomejs.biome"
"editor.defaultFormatter": "oxc.oxc-vscode"
},
"[typescriptreact]": {
"editor.defaultFormatter": "oxc.oxc-vscode"
},
"[javascript]": {
"editor.defaultFormatter": "oxc.oxc-vscode"
},
"cSpell.words": [
"atlassify",
"codegen",
"partialize",
"rovo",
"xcss",
"aptabase"
"aptabase",
"oxfmt",
"oxlint"
],
"i18n-ally.translate.engines": ["google"],
"i18n-ally.localesPaths": ["src/renderer/i18n/locales"],
Expand Down
30 changes: 20 additions & 10 deletions ARCHITECTURE.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
# Atlassify Project Architecture

## Overview
Atlassify is a cross-platform desktop app for monitoring notifications from Atlassian Cloud products. Built with Electron, React, and TypeScript, it provides a fast, native experience with customizable filters, themes, and tray/menu bar integration.

Atlassify is a cross-platform desktop app for monitoring notifications from Atlassian Cloud products. Built with Electron, React, and TypeScript, it provides a fast, native experience with customizable filters, themes, and tray/menu bar integration.

## Folder Structure

```
/
├── src/
Expand All @@ -20,61 +21,64 @@ Atlassify is a cross-platform desktop app for monitoring notifications from Atla
├── ... # Config files, project metadata
```


## Core Components

- **Main Process (`src/main`)**: Handles app lifecycle, system events, tray/menu bar, auto-launch, updater, and IPC communication.
- **Preload (`src/preload`)**: Secure bridge exposing whitelisted APIs to the renderer.
- **Renderer (`src/renderer`)**: React-based UI, state management (stores via [Zustand](https://github.com/pmndrs/zustand)), hooks, components, and settings. Uses [TanStack Query](https://tanstack.com/query/latest) for data fetching and caching.
- **Shared (`src/shared`)**: Common utilities, types, and constants used across main, preload, and renderer.


## State Management

The app uses a deliberate three-layer approach. Use the following decision rule when choosing where state belongs:

| Layer | Use when | Examples |
|---|---|---|
| **Zustand (persisted)** | Data must survive app restarts; serialisable config or credentials | `useAccountsStore`, `useFiltersStore`, `useSettingsStore` |
| **Zustand (non-persisted)** | Volatile state that must be readable *outside* React (e.g. tray subscriptions) | `useRuntimeStore` |
| **AppContext + React Query** | Ephemeral, async, React-lifecycle-bound state; cannot be serialised | notifications, loading/error status, mutations |
| Layer | Use when | Examples |
| ---------------------------- | ------------------------------------------------------------------------------ | --------------------------------------------------------- |
| **Zustand (persisted)** | Data must survive app restarts; serialisable config or credentials | `useAccountsStore`, `useFiltersStore`, `useSettingsStore` |
| **Zustand (non-persisted)** | Volatile state that must be readable _outside_ React (e.g. tray subscriptions) | `useRuntimeStore` |
| **AppContext + React Query** | Ephemeral, async, React-lifecycle-bound state; cannot be serialised | notifications, loading/error status, mutations |

**Persisted Zustand stores** (`src/renderer/stores`):

- `useAccountsStore` — Authenticated accounts; tokens are encrypted via `electron.safeStorage` before being written to local storage.
- `useFiltersStore` — Active notification filter values (products, categories, read-states, etc.).
- `useSettingsStore` — All UI and behaviour preferences (theme, zoom, shortcuts).

**Non-persisted Zustand store**:

- `useRuntimeStore` — The React→Electron bridge. The `useNotifications` hook writes filtered counts and error state into this store each render, so that `subscriptions.ts` (which runs outside of React) can read pre-filtered values without re-applying filter logic or touching the React Query cache.

**AppContext** (`src/renderer/context/AppContext.tsx`):

- Acts as a façade/coordinator, not a `useState` holder. It composes hooks and stores into a single stable, memoised context value that route-level components consume via `useAppContext()`.
- Does **not** hold persistent config — components that need settings, filters, or account data access those stores directly, avoiding unnecessary re-renders.

**Subscriptions** (`src/renderer/stores/subscriptions.ts`): Store subscriptions synchronize state with Electron APIs and system events (e.g. updating the tray icon when notification counts change).


## Hooks

All hooks live in `src/renderer/hooks`. Each hook has a single, named responsibility.

**Data fetching**:

- `useNotifications` — Fetches, filters, and caches notifications via React Query. Applies `useFiltersStore` values as a `select` transformer (instant filtering, no re-fetch). Writes filtered counts into `useRuntimeStore` for tray updates.
- `useAccounts` — Periodically refreshes authenticated account profiles (1hr interval).

**Lifecycle & side-effects** (called inside `AppProvider`):

- `useOnlineSync` — Subscribes to TanStack Query's `onlineManager` and keeps `useRuntimeStore.isOnline` in sync. Also corrects `onlineManager`'s initial state from `navigator.onLine` on mount.
- `useAppReset` — Listens for the `onResetApp` IPC event and calls `reset()` on every persisted store. Add new persisted stores here.
- `useKeyboardNavigation` — Tracks the focused notification ID for keyboard traversal.
- `useGlobalShortcuts` — Registers system-level keyboard shortcuts from settings.

**Utilities**:

- `useIntervalTimer` — Wraps `setInterval` for reliable background polling regardless of window visibility (see polling strategy below).
- `useNavigationAnalytics` — Tracks route transitions.
- `useAppContext` — Type-safe accessor for `AppContext`; throws if used outside `AppProvider`.

**IPC Communication**: Main ↔ Preload ↔ Renderer via secure, typed IPC channels for data, commands, and notifications.


## Data Flow

- **Background Polling Strategy**:
Expand All @@ -90,33 +94,39 @@ All hooks live in `src/renderer/hooks`. Each hook has a single, named responsibi
- **Animation Coordination**: Components manage their own exit animations independently of cache updates, decoupling UI timing from data layer.

## UI

- **Component Library**: Uses [Atlassian @atlaskit](https://atlassian.design/components/) for UI components, ensuring a native Atlassian look and feel.
- **Styling**: [Tailwind CSS](https://tailwindcss.com/) is used for utility-first styling and layout.
- **Design Tokens**: Atlassian design tokens are integrated for consistent theming and design language across the app.

## API & Integrations

- **Atlassian API**: Accessed via secure requests, using API tokens with scopes.
- **State & Query**: [Zustand](https://github.com/pmndrs/zustand) for state management, [TanStack Query](https://tanstack.com/query/latest) for server state and caching.
- **Third-party Integrations**: Homebrew, Netlify, SonarCloud, GitHub Actions for CI/CD and releases.

## Build & Deployment

- **Build Tools**: Vite for frontend, Electron Builder for packaging.
- **Testing**: [Vitest](https://vitest.dev/) for unit tests and coverage.
- **CI/CD**: Automated workflows for linting, testing, building, signing, and releasing.
- **Release Process**: See CONTRIBUTING.md for details.

## Extensibility & Customization

- **Locales**: Add new language files in `src/renderer/i18n/locales`.
- **Themes & Filters**: Customizable via settings store and UI.
- **Components**: Modular React components for easy extension.

## Key Patterns & Conventions

- **TypeScript**: Strong typing throughout.
- **Biome**: Linting and formatting.
- **Testing**: Vitest for unit tests, coverage tracked.
- **Commit Style**: Clear, descriptive commit messages.

## Useful Links

- [CONTRIBUTING.md](CONTRIBUTING.md)
- [README.md](README.md)
- [GitHub Issues](https://github.com/setchy/atlassify/issues)
Loading