diff --git a/.github/labeler.yml b/.github/labeler.yml index 454edc2ba..587ff935d 100644 --- a/.github/labeler.yml +++ b/.github/labeler.yml @@ -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 @@ -46,7 +46,7 @@ labels: sync: true matcher: title: '^test(\(\w+\))?: .*' - + checks: - context: 'Semantic Pull Request' description: @@ -62,4 +62,4 @@ checks: - build - release - chore - - dependency \ No newline at end of file + - dependency diff --git a/.github/release.yml b/.github/release.yml index 182ae71e4..02d0b9250 100644 --- a/.github/release.yml +++ b/.github/release.yml @@ -29,4 +29,4 @@ changelog: - chore - title: Other Changes labels: - - "*" \ No newline at end of file + - '*' diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9640a8e2f..1da15896d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -5,12 +5,12 @@ on: branches: - main paths-ignore: - - "docs/**" + - 'docs/**' pull_request: branches: - main paths-ignore: - - "docs/**" + - 'docs/**' permissions: {} @@ -32,7 +32,7 @@ jobs: permissions: contents: read with: - directory: "." + directory: '.' tests: name: Tests diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index fa96e1bae..18d049be3 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -5,10 +5,10 @@ on: branches: - main paths: - - "docs/**" + - 'docs/**' pull_request: paths: - - "docs/**" + - 'docs/**' permissions: {} @@ -19,4 +19,4 @@ jobs: permissions: contents: read with: - directory: "docs" + directory: 'docs' diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 573463488..af9dbd6bf 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -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 @@ -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: diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 08b15fd07..afed50705 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -14,7 +14,7 @@ jobs: permissions: contents: read with: - directory: "." + directory: '.' tests: name: Tests diff --git a/.github/workflows/renovate.yml b/.github/workflows/renovate.yml index a11dcd03a..4dfe1f0c1 100644 --- a/.github/workflows/renovate.yml +++ b/.github/workflows/renovate.yml @@ -23,10 +23,13 @@ jobs: uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 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 diff --git a/.github/workflows/zizmor.yml b/.github/workflows/zizmor.yml index fe29e206b..64b2d50af 100644 --- a/.github/workflows/zizmor.yml +++ b/.github/workflows/zizmor.yml @@ -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@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - 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@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + persist-credentials: false - - name: Run zizmor 🌈 - uses: zizmorcore/zizmor-action@192e21d79ab29983730a13d1382995c2307fbcaa # v0.5.7 + - name: Run zizmor 🌈 + uses: zizmorcore/zizmor-action@192e21d79ab29983730a13d1382995c2307fbcaa # v0.5.7 diff --git a/.husky/pre-commit b/.husky/pre-commit index 8b3e5b645..6606e9a49 100644 --- a/.husky/pre-commit +++ b/.husky/pre-commit @@ -1 +1 @@ -pnpm dlx lint-staged +pnpm exec vp staged diff --git a/.vscode/extensions.json b/.vscode/extensions.json index 62a02adca..474347c07 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -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" diff --git a/.vscode/settings.json b/.vscode/settings.json index 0b146dc31..7b62b90af 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,27 +1,27 @@ { "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" - }, - "[typescript]": { - "editor.defaultFormatter": "biomejs.biome" - }, "cSpell.words": [ "atlassify", "codegen", "partialize", "rovo", "xcss", - "aptabase" + "aptabase", + "oxfmt", + "oxlint" ], "i18n-ally.translate.engines": ["google"], "i18n-ally.localesPaths": ["src/renderer/i18n/locales"], diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index fdf305451..32b0765ad 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -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/ @@ -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**: @@ -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) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index f6a8bb5ce..d427ca815 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,6 +1,6 @@ # Atlassify Contributing Guide -Hi, we're really excited that you're interested in contributing to Atlassify! +Hi, we're really excited that you're interested in contributing to Atlassify! Before submitting your contribution, please read through the following guide. @@ -13,24 +13,27 @@ This project is a tool for monitoring new notifications from Atlassian Cloud pro To get started: Clone the repository and install dependencies: - ```shell - pnpm install - ``` + +```shell +pnpm install +``` Start development mode (includes GraphQL codegen and hot module reload): - ```shell - pnpm dev - ``` + +```shell +pnpm dev +``` ### Tests -There are two main checks: -1. Linter & formatter with [Biome][biome-website] -2. Unit tests with [Vitest][vitest-website] +Tooling is unified through [Vite+][vite-plus-website], which bundles the linter (oxlint), formatter (oxfmt), test runner (Vitest), and dev/build pipeline (Vite). ```shell -# Run biome to check linting and formatting -pnpm lint:check +# Run lint, format, and type checks +pnpm check + +# Auto-fix formatting and lint issues +pnpm check:fix # Run unit tests with coverage pnpm test @@ -41,7 +44,7 @@ pnpm test -u ### Code Style & Conventions -- We use [Biome][biome-website] for linting and formatting. Please run `pnpm lint:check` before submitting a PR. +- Linting and formatting are configured in `vite.config.ts` (the `lint` and `fmt` blocks). Please run `pnpm check` before submitting a PR. - Follow existing file and folder naming conventions. - Keep commit messages clear and descriptive. @@ -56,37 +59,42 @@ The release process is automated. Follow the steps below. 1. **Verify features:** Ensure all features and fixes you want included in the release are merged into `main`. 2. **Check dependencies:** Review the [Renovate Dependency Dashboard][github-dependency-dashboard] for any dependency updates you want to include. 3. **Create a release branch:** - - Name your branch `release/vX.X.X` (e.g., `release/v1.2.3`). - - Run `pnpm version ` to **bump the version** in `package.json` and create a version commit/tag. - - Update `sonar.projectVersion` within `sonar-project.properties` - - Commit and push these changes. - - Open a Pull Request (PR) from your release branch. + +- Name your branch `release/vX.X.X` (e.g., `release/v1.2.3`). +- Run `pnpm version ` to **bump the version** in `package.json` and create a version commit/tag. +- Update `sonar.projectVersion` within `sonar-project.properties` +- Commit and push these changes. +- Open a Pull Request (PR) from your release branch. + 4. **GitHub release:** GitHub Actions will automatically build, sign, and upload release assets to a new draft release with automated release notes. 5. **Merge the release branch:** Once the PR is approved and checks pass, merge your release branch into `main`. 6. **Publish the release:** - - Finalize the release notes in the draft release on GitHub. - - Confirm all assets are present and correct. - - Publish the release. + +- Finalize the release notes in the draft release on GitHub. +- Confirm all assets are present and correct. +- Publish the release. + 7. **Update milestones:** - - Edit the current [Milestone][github-milestones]: - - Add a link to the release notes in the description. - - Set the due date to the release date. - - Close the milestone. - - Create a [New Milestone][github-new-milestone] for the next release cycle. +- Edit the current [Milestone][github-milestones]: + - Add a link to the release notes in the description. + - Set the due date to the release date. + - Close the milestone. +- Create a [New Milestone][github-new-milestone] for the next release cycle. ### Locales Atlassify supports multiple languages/locales. To add a new locale: + - Add a new locale file under `./src/renderer/i18n/locales`. - Import and update the resources in `./src/renderer/i18n/index.ts`. - Use the VSCode Extension `i18n Ally` to automatically translate the keys. -- Carefully verify the translated values. **Do not translate placeholder variables** like `{{ }}`. You may need to manually update these if needed. - +- Carefully verify the translated values. **Do not translate placeholder variables** like `{{ }}`. You may need to manually update these if needed. + [biome-website]: https://biomejs.dev/ [github-dependency-dashboard]: https://github.com/setchy/atlassify/issues/1 [github-issues]: https://github.com/setchy/atlassify/issues @@ -95,4 +103,3 @@ To add a new locale: [github-new-release]: https://github.com/setchy/atlassify/releases/new [homebrew-cask-autobump-workflow]: https://github.com/Homebrew/homebrew-cask/actions/workflows/autobump.yml [vitest-website]: https://vitest.dev/ - diff --git a/PRIVACY.md b/PRIVACY.md index d738e7b5d..7d49399cf 100644 --- a/PRIVACY.md +++ b/PRIVACY.md @@ -75,5 +75,6 @@ https://github.com/setchy/atlassify/issues This policy may be updated over time. Material changes will be reflected by updating the date at the top of this file. + [aptabase-website]: https://aptabase.com/ -[google-analytics-website]: https://developers.google.com/analytics \ No newline at end of file +[google-analytics-website]: https://developers.google.com/analytics diff --git a/README.md b/README.md index 5ea60507a..4e62effc6 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,6 @@ --- - ## Features - πŸ”” Unified notifications from Atlassian Cloud products @@ -18,20 +17,20 @@ - ⚑ Fast, native experience - 🌐 Multi-language/localization support - ## Quick Start 1. **Download** Atlassify for free from [atlassify.io][website]. 2. **Install** and launch the app for your platform. 3. **Authenticate** with your Atlassian account and start receiving notifications. -#### Experimental +#### Experimental + macOS users can also install via [Homebrew][brew] + ```shell brew install --cask setchy/brews/atlassify ``` - ## Build & Development To build and run Atlassify locally: @@ -44,32 +43,27 @@ pnpm dev See [CONTRIBUTING.md](CONTRIBUTING.md) for full development and contribution instructions. - ## FAQ See our [Atlassify FAQs][faqs] for answers to common questions. - ## Privacy & Analytics Atlassify includes optional anonymous usage analytics to help improve product quality and UX. See [PRIVACY.md](PRIVACY.md) for complete details. - ## Community & Support - Open an [issue][github-issues] for bugs or feature requests. - See [CONTRIBUTING.md](CONTRIBUTING.md) for more ways to get involved. - ## License -Atlassify is licensed under the MIT Open Source license. +Atlassify is licensed under the MIT Open Source license. See [LICENSE](LICENSE) for details. - ## Acknowledgements I would like to acknowledge the following projects and resources that have inspired and contributed to the development of Atlassify: @@ -77,15 +71,13 @@ I would like to acknowledge the following projects and resources that have inspi 1. [Gitify][attribution-gitify] – An open-source GitHub notification app, which I am the lead maintainer of, served as the launchpad for Atlassify. 2. [Atlassian Design System][attribution-atlassian] – The design principles and UI components (@atlaskit) from Atlassian have helped shape the user interface of Atlassify. - + [social]: docs/public/images//social.png [website]: https://atlassify.io [faqs]: https://atlassify.io/faq - [attribution-gitify]: https://gitify.io [attribution-atlassian]: https://atlassian.design - [github]: https://github.com/setchy/atlassify [github-actions]: https://github.com/setchy/atlassify/actions [github-issues]: https://github.com/setchy/atlassify/issues @@ -93,12 +85,10 @@ I would like to acknowledge the following projects and resources that have inspi [github-website]: https://github.com/setchy/atlassify-website [github-website-pulls]: https://github.com/setchy/atlassify-website/pulls [brew]: https://brew.sh - [coverage-badge]: https://img.shields.io/sonar/coverage/setchy_atlassify?server=https%3A%2F%2Fsonarcloud.io&logo=sonarqubecloud [coverage]: https://sonarcloud.io/summary/new_code?id=setchy_atlassify [quality-badge]: https://img.shields.io/sonar/quality_gate/setchy_atlassify?server=https%3A%2F%2Fsonarcloud.io&logo=sonarqubecloud [quality]: https://sonarcloud.io/summary/new_code?id=setchy_atlassify - [ci-workflow-badge]: https://img.shields.io/github/actions/workflow/status/setchy/atlassify/ci.yml?logo=github&label=CI [release-workflow-badge]: https://img.shields.io/github/actions/workflow/status/setchy/atlassify/release.yml?logo=github&label=Release [downloads-total-badge]: https://img.shields.io/github/downloads/setchy/atlassify/total?label=downloads@all&logo=github diff --git a/ROADMAP.md b/ROADMAP.md index 4f63c9a12..b58a306e5 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -1,13 +1,15 @@ -# Atlassify Roadmap +# Atlassify Roadmap -Please see our [GitHub Issues registry][github-issues] for items we want to incorporate into the product offering in the near future. +Please see our [GitHub Issues registry][github-issues] for items we want to incorporate into the product offering in the near future. [Contributions][contributing] are always welcomed! # Atlassian Roadmap + - https://www.atlassian.com/wac/roadmap/cloud?&search=Atlassian%20Home - https://www.atlassian.com/wac/roadmap/cloud/work-suggestions-for-atlassian-home?p=8721f558-a6 + [contributing]: CONTRIBUTING -[github-issues]: https://github.com/setchy/atlassify/issues \ No newline at end of file +[github-issues]: https://github.com/setchy/atlassify/issues diff --git a/assets/images/README.md b/assets/images/README.md index aa72160d3..b250a9023 100644 --- a/assets/images/README.md +++ b/assets/images/README.md @@ -1,3 +1,3 @@ # Resources -- https://atlassian.design/resources/logo-library \ No newline at end of file +- https://atlassian.design/resources/logo-library diff --git a/biome.json b/biome.json deleted file mode 100644 index a7e72dbd8..000000000 --- a/biome.json +++ /dev/null @@ -1,137 +0,0 @@ -{ - "$schema": "https://biomejs.dev/schemas/2.5.2/schema.json", - "files": { - "includes": ["**", "!**/docs/**/*", "!**/generated/**/*"] - }, - "assist": { - "actions": { - "source": { - "organizeImports": { - "level": "on", - "options": { - "groups": [ - ":NODE:", - ":BLANK_LINE:", - ["react*", "@testing-library/**"], - ":BLANK_LINE:", - ["*electron*", "menubar"], - ":BLANK_LINE:", - "@atlaskit/**", - ":BLANK_LINE:", - ":PACKAGE:", - ":BLANK_LINE:", - ["**/__mocks__/**", "**/__helpers__/**"], - ":BLANK_LINE:", - "**/shared/**", - ":BLANK_LINE:", - ["**/constants", "**/constants/**"], - ":BLANK_LINE:", - [ - "**/context/**", - "**/hooks/**", - "**/routes/**", - "**/stores", - "**/stores/**" - ], - ":BLANK_LINE:", - [ - "**/layout/**", - "**/components/**", - "**/fields/**", - "**/primitives/**" - ], - ":BLANK_LINE:", - "**/types", - ":BLANK_LINE:", - "**" - ] - } - }, - "useSortedAttributes": "on" - } - } - }, - "linter": { - "enabled": true, - "domains": { - "react": "recommended", - "test": "recommended" - }, - "rules": { - "preset": "recommended", - "correctness": { - "noUnusedFunctionParameters": "error", - "useExhaustiveDependencies": { - "level": "warn", - "options": { - "hooks": [ - { - "name": "useNavigate", - "stableResult": true - } - ] - } - }, - "useHookAtTopLevel": { - "level": "error", - "options": { - "ignore": ["useAlternateIdleIcon", "useUnreadActiveIcon"] - } - } - }, - "style": { - "noInferrableTypes": "error", - "noParameterAssign": "error", - "noUnusedTemplateLiteral": "error", - "noUselessElse": "error", - "useAsConstAssertion": "error", - "useBlockStatements": "error", - "useDefaultParameterLast": "error", - "useDefaultSwitchClause": "error", - "useEnumInitializers": "error", - "useNumberNamespace": "error", - "useSelfClosingElements": "error", - "useSingleVarDeclarator": "error" - }, - "suspicious": { - "noConsole": "error", - "noUnknownAtRules": "warn" - } - } - }, - "formatter": { - "enabled": true, - "indentStyle": "space", - "indentWidth": 2 - }, - "css": { - "formatter": { - "enabled": true - }, - "parser": { - "tailwindDirectives": true - } - }, - "html": { - "formatter": { - "enabled": true - }, - "experimentalFullSupportEnabled": true - }, - "javascript": { - "formatter": { - "quoteStyle": "single", - "jsxQuoteStyle": "double" - } - }, - "json": { - "parser": { - "allowComments": true - } - }, - "vcs": { - "enabled": true, - "clientKind": "git", - "useIgnoreFile": true - } -} diff --git a/docs/.gitignore b/docs/.gitignore new file mode 100644 index 000000000..3974d6581 --- /dev/null +++ b/docs/.gitignore @@ -0,0 +1,6 @@ +# build output +dist/ +.astro/ + +# dependencies +node_modules/ diff --git a/docs/README.md b/docs/README.md index fb758adfe..20ad0669c 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,4 +1,4 @@ -# atlassify.io +# atlassify.io > The source code for our atlassify.io website diff --git a/docs/biome.json b/docs/biome.json index 180d63b38..d760dad2b 100644 --- a/docs/biome.json +++ b/docs/biome.json @@ -1,5 +1,5 @@ { - "root": false, + "root": true, "$schema": "https://biomejs.dev/schemas/2.5.2/schema.json", "assist": { "actions": { diff --git a/docs/package.json b/docs/package.json index 474bfef1a..2b1444eff 100644 --- a/docs/package.json +++ b/docs/package.json @@ -3,37 +3,37 @@ "version": "latest", "private": true, "description": "The source code of atlassify.io", - "type": "module", - "scripts": { - "prepare": "cd .. && husky", - "dev": "astro dev", - "start": "pnpm dev", - "build": "astro build", - "preview": "astro preview", - "lint": "biome check --fix", - "lint:check": "biome check" - }, - "repository": { - "type": "git", - "url": "https://github.com/setchy/atlassify" - }, "keywords": [ - "atlassify", + "astro", "atlassian", - "notifier", + "atlassify", "notifications", - "website", - "astro" + "notifier", + "website" ], + "homepage": "https://atlassify.io/", + "bugs": { + "url": "https://github.com/setchy/atlassify/issues" + }, + "license": "MIT", "author": { "name": "Adam Setch", "url": "https://github.com/setchy" }, - "license": "MIT", - "bugs": { - "url": "https://github.com/setchy/atlassify/issues" + "repository": { + "type": "git", + "url": "https://github.com/setchy/atlassify" + }, + "type": "module", + "scripts": { + "prepare": "cd .. && husky", + "dev": "astro dev", + "start": "pnpm dev", + "build": "astro build", + "preview": "astro preview", + "check": "biome check", + "check:fix": "biome check --fix" }, - "homepage": "https://atlassify.io/", "dependencies": { "@iconify-json/line-md": "1.2.16", "@iconify-json/mdi": "1.2.3", @@ -56,8 +56,5 @@ "@biomejs/biome": "2.5.2", "husky": "9.1.7" }, - "packageManager": "pnpm@11.9.0", - "lint-staged": { - "*.{js,ts,cjs,mjs,astro}": "biome format --fix" - } + "packageManager": "pnpm@11.9.0" } diff --git a/docs/src/components/GitHubRepo.astro b/docs/src/components/GitHubRepo.astro index 2c0c34f6e..1f5b8e82c 100644 --- a/docs/src/components/GitHubRepo.astro +++ b/docs/src/components/GitHubRepo.astro @@ -34,6 +34,7 @@ const loadRepoStats = async (): Promise => { latestReleaseName: latestRelease.data.name?.replace('v', '') ?? '', }; } catch (err) { + // oxlint-disable-next-line no-console console.error('Failed to load repo stats', err); return { forks: '', diff --git a/docs/src/faqs/1.md b/docs/src/faqs/1.md index f8e8478ae..08bc0f0fb 100644 --- a/docs/src/faqs/1.md +++ b/docs/src/faqs/1.md @@ -1,7 +1,9 @@ --- -title: "How do I authenticate Atlassify with my Atlassian account?" +title: 'How do I authenticate Atlassify with my Atlassian account?' --- + Atlassify currently supports authentication via Atlassian API Tokens, either: + - βœ… API token with scopes **[strongly recommended]** - ⚠️ API token _[supported, but not recommended]_ @@ -11,4 +13,4 @@ When authenticating with **API token with scopes** please choose the following J
-Please refer to the [official docs](https://support.atlassian.com/atlassian-account/docs/manage-api-tokens-for-your-atlassian-account/#Create-an-API-token-with-scopes) for help creating and managing an **API token with scopes**. \ No newline at end of file +Please refer to the [official docs](https://support.atlassian.com/atlassian-account/docs/manage-api-tokens-for-your-atlassian-account/#Create-an-API-token-with-scopes) for help creating and managing an **API token with scopes**. diff --git a/docs/src/faqs/2.md b/docs/src/faqs/2.md index ac83744e4..9416de21b 100644 --- a/docs/src/faqs/2.md +++ b/docs/src/faqs/2.md @@ -1,5 +1,5 @@ --- -title: "How do Filters work?" +title: 'How do Filters work?' --- Filters in Atlassify help you manage which notifications are shown in the app, allowing you to concentrate on the most crucial ones for your workflow. @@ -11,4 +11,3 @@ Filters can be accessed from the Atlassify sidebar. By default, all notification
If you apply filters, your inbox will display notifications based on a `LOGICAL AND` condition. - diff --git a/docs/src/faqs/3.md b/docs/src/faqs/3.md index cfd52358d..3d14b3a8b 100644 --- a/docs/src/faqs/3.md +++ b/docs/src/faqs/3.md @@ -1,7 +1,9 @@ --- -title: "Something is not working as expected, how can I debug Atlassify?" +title: 'Something is not working as expected, how can I debug Atlassify?' --- + Using **Chrome Developer Tools** (console logs, network requests, etc): + - All platforms: right click tray icon then _Developer β†’ Toggle Developer Tools_ - macOS: `command + opt + i` - Windows: `ctrl + shift + i` @@ -10,7 +12,8 @@ Using **Chrome Developer Tools** (console logs, network requests, etc):
Using **Application Log Files**: + - All platforms: right click tray icon then _Developer β†’ View Application Logs_ - macOS: `~/Library/Logs/atlassify` - Windows: `%USERPROFILE%\\AppData\\Roaming\\atlassify\\logs` -- Linux: `~/.config/atlassify/logs` \ No newline at end of file +- Linux: `~/.config/atlassify/logs` diff --git a/docs/src/faqs/4.md b/docs/src/faqs/4.md index f5afeab2e..684393015 100644 --- a/docs/src/faqs/4.md +++ b/docs/src/faqs/4.md @@ -1,4 +1,5 @@ --- -title: "Does Atlassify support Atlassian Data Center or Server products?" +title: 'Does Atlassify support Atlassian Data Center or Server products?' --- -Currently Atlassify supports Atlassian Cloud products. If you would like to see support for other product types, please raise a [GitHub issue](https://github.com/setchy/atlassify/issues). \ No newline at end of file + +Currently Atlassify supports Atlassian Cloud products. If you would like to see support for other product types, please raise a [GitHub issue](https://github.com/setchy/atlassify/issues). diff --git a/docs/src/faqs/5.md b/docs/src/faqs/5.md index 02aa1b811..e87c1e814 100644 --- a/docs/src/faqs/5.md +++ b/docs/src/faqs/5.md @@ -1,8 +1,9 @@ --- -title: "How can I contribute to Atlassify?" +title: 'How can I contribute to Atlassify?' --- + You can contribute to Atlassify by opening an issue or pull request on GitHub at [setchy/atlassify](https://github.com/setchy/atlassify).
-Check out our [open issues](https://github.com/setchy/atlassify/issues) and see if there is any existing ideas that you would like to work on. \ No newline at end of file +Check out our [open issues](https://github.com/setchy/atlassify/issues) and see if there is any existing ideas that you would like to work on. diff --git a/index.html b/index.html new file mode 100644 index 000000000..54cf04086 --- /dev/null +++ b/index.html @@ -0,0 +1,9 @@ + + + + vite-plugin-electron + + +
An entry file for electron renderer process.
+ + \ No newline at end of file diff --git a/netlify.toml b/netlify.toml index af9d86363..caada32d8 100644 --- a/netlify.toml +++ b/netlify.toml @@ -1,7 +1,6 @@ [build] - base = "docs" - publish = "dist" - command = "pnpm build" - - # Only build when there are changes in docs/ - ignore = "git diff --quiet $CACHED_COMMIT_REF $COMMIT_REF -- docs/" \ No newline at end of file +base = "docs" +publish = "dist" +command = "pnpm build" +# Only build when there are changes in docs/ +ignore = "git diff --quiet $CACHED_COMMIT_REF $COMMIT_REF -- docs/" diff --git a/package.json b/package.json index 5a8329adc..7e93f3764 100644 --- a/package.json +++ b/package.json @@ -3,6 +3,32 @@ "version": "3.11.0", "private": true, "description": "Atlassian notifications on your menu bar.", + "keywords": [ + "atlassian", + "atlassify", + "bitbucket", + "compass", + "confluence", + "electron", + "jira", + "menubar", + "notifications", + "taskbar", + "tray" + ], + "homepage": "https://atlassify.io", + "bugs": { + "url": "https://github.com/setchy/atlassify/issues" + }, + "license": "MIT", + "author": { + "name": "Adam Setch", + "url": "https://github.com/setchy" + }, + "repository": { + "type": "git", + "url": "https://github.com/setchy/atlassify.git" + }, "main": "build/main.js", "scripts": { "clean": "rimraf build coverage dist node_modules", @@ -20,41 +46,16 @@ "i18n:check": "i18next-cli extract --ci --quiet", "i18n:lint": "i18next-cli lint", "i18n:status": "i18next-cli status", - "lint": "biome check --fix", - "lint:check": "biome check", - "test": "vitest --coverage --run", - "test:watch": "vitest --watch --coverage", - "test:ui": "vitest --ui" - }, - "engines": { - "node": ">=24" + "check": "vp check", + "check:fix": "vp check --fix", + "lint": "vp lint --fix", + "lint:check": "vp lint", + "format": "vp fmt", + "format:check": "vp fmt --check", + "test": "vp test --coverage --run", + "test:watch": "vp test --watch --coverage", + "test:ui": "vp test --ui" }, - "repository": { - "type": "git", - "url": "https://github.com/setchy/atlassify.git" - }, - "keywords": [ - "atlassify", - "atlassian", - "bitbucket", - "compass", - "confluence", - "jira", - "notifications", - "electron", - "menubar", - "taskbar", - "tray" - ], - "author": { - "name": "Adam Setch", - "url": "https://github.com/setchy" - }, - "license": "MIT", - "bugs": { - "url": "https://github.com/setchy/atlassify/issues" - }, - "homepage": "https://atlassify.io", "dependencies": { "electron-log": "5.4.4", "electron-menubar": "10.1.1", @@ -90,7 +91,6 @@ "@atlaskit/toggle": "17.1.0", "@atlaskit/tokens": "15.3.1", "@atlaskit/tooltip": "23.1.0", - "@biomejs/biome": "2.5.2", "@compiled/vite-plugin": "1.1.6", "@discordapp/twemoji": "16.0.1", "@electron/notarize": "3.1.1", @@ -132,16 +132,9 @@ "vite": "8.1.3", "vite-plugin-checker": "0.14.4", "vite-plugin-electron": "1.1.0", + "vite-plus": "0.2.2", "vitest": "4.1.9", "zustand": "5.0.14" }, - "packageManager": "pnpm@11.9.0", - "lint-staged": { - "*": "biome check --fix --no-errors-on-unmatched", - "*.{js,ts,tsx}": [ - "bash -c 'tsc --noEmit'", - "bash -c 'pnpm i18n:lint'", - "pnpm test --changed --passWithNoTests --update" - ] - } + "packageManager": "pnpm@11.9.0" } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d26dbb9b0..c35bf981c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -111,9 +111,6 @@ importers: '@atlaskit/tooltip': specifier: 23.1.0 version: 23.1.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@biomejs/biome': - specifier: 2.5.2 - version: 2.5.2 '@compiled/vite-plugin': specifier: 1.1.6 version: 1.1.6(vite@8.1.3(@types/node@24.13.2)(jiti@2.7.0)(yaml@2.9.0)) @@ -176,7 +173,7 @@ importers: version: 6.0.3(vite@8.1.3(@types/node@24.13.2)(jiti@2.7.0)(yaml@2.9.0)) '@vitest/coverage-v8': specifier: 4.1.9 - version: 4.1.9(vitest@4.1.9) + version: 4.1.9(@vitest/browser@4.1.9)(vitest@4.1.9) '@vitest/ui': specifier: 4.1.9 version: 4.1.9(vitest@4.1.9) @@ -233,13 +230,16 @@ importers: version: 8.1.3(@types/node@24.13.2)(jiti@2.7.0)(yaml@2.9.0) vite-plugin-checker: specifier: 0.14.4 - version: 0.14.4(@biomejs/biome@2.5.2)(typescript@6.0.3)(vite@8.1.3(@types/node@24.13.2)(jiti@2.7.0)(yaml@2.9.0)) + version: 0.14.4(@biomejs/biome@2.5.2)(oxlint@1.72.0(oxlint-tsgolint@0.24.0)(vite-plus@0.2.2(@types/node@24.13.2)(@vitest/coverage-v8@4.1.9)(@vitest/ui@4.1.9)(happy-dom@20.10.6)(jiti@2.7.0)(typescript@6.0.3)(vite@8.1.3(@types/node@24.13.2)(jiti@2.7.0)(yaml@2.9.0))(yaml@2.9.0)))(typescript@6.0.3)(vite@8.1.3(@types/node@24.13.2)(jiti@2.7.0)(yaml@2.9.0)) vite-plugin-electron: specifier: 1.1.0 version: 1.1.0 + vite-plus: + specifier: 0.2.2 + version: 0.2.2(@types/node@24.13.2)(@vitest/coverage-v8@4.1.9)(@vitest/ui@4.1.9)(happy-dom@20.10.6)(jiti@2.7.0)(typescript@6.0.3)(vite@8.1.3(@types/node@24.13.2)(jiti@2.7.0)(yaml@2.9.0))(yaml@2.9.0) vitest: specifier: 4.1.9 - version: 4.1.9(@types/node@24.13.2)(@vitest/coverage-v8@4.1.9)(@vitest/ui@4.1.9)(happy-dom@20.10.6)(vite@8.1.3(@types/node@24.13.2)(jiti@2.7.0)(yaml@2.9.0)) + version: 4.1.9(@types/node@24.13.2)(@vitest/browser-preview@4.1.9)(@vitest/coverage-v8@4.1.9)(@vitest/ui@4.1.9)(happy-dom@20.10.6)(vite@8.1.3(@types/node@24.13.2)(jiti@2.7.0)(yaml@2.9.0)) zustand: specifier: 5.0.14 version: 5.0.14(@types/react@19.2.17)(react@19.2.7)(use-sync-external-store@1.6.0(react@19.2.7)) @@ -674,6 +674,9 @@ packages: cpu: [x64] os: [win32] + '@blazediff/core@1.9.1': + resolution: {integrity: sha512-ehg3jIkYKulZh+8om/O25vkvSsXXwC+skXmyA87FFx6A/45eqOkZsBltMw/TVteb0mloiGT8oGRTcjRAz66zaA==} + '@compiled/babel-plugin-strip-runtime@0.40.1': resolution: {integrity: sha512-SQ7fopcYKisaHh3nlBsAnt38FMDgiw5pjGxRAtsA08RVBakzWfLQhJ1wvIo9pmLz0s6+ew/sC3E9l7TDuxM3UQ==} @@ -1268,9 +1271,291 @@ packages: '@open-draft/until@2.1.0': resolution: {integrity: sha512-U69T3ItWHvLwGg5eJ0n3I62nWuE6ilHlmz7zM0npLBRvPRd7e6NYmg54vvRtP5mZG7kZqZCFVdsTWo7BPtBujg==} + '@oxc-project/runtime@0.138.0': + resolution: {integrity: sha512-yHhoXsN8tYxgdJCdD91PbySNjEEaBX/tH2OQRDXJpsQv5b184oC4/qVbU7qlblvfil/JP15Lh2HW7+HN5DS90Q==} + engines: {node: ^20.19.0 || >=22.12.0} + '@oxc-project/types@0.138.0': resolution: {integrity: sha512-1a7ZKmrRTCoN1XMZ4L0PyyqrMnrNlLyPuOkdSX2MZg7IiIGRUyurNhAm73ptDOraoBcIordsIGKNPKUzy3ZmfA==} + '@oxfmt/binding-android-arm-eabi@0.57.0': + resolution: {integrity: sha512-qVBsEO+KugOsCmUHcO8iqNnqc65p7PCKpCs8M66mPZ+Ri+CWbcpoQOEJBg2OTu03+0qu++NK1jj6IzvQVs0Sig==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [android] + + '@oxfmt/binding-android-arm64@0.57.0': + resolution: {integrity: sha512-mp6PibWbao3aizijcheOeHQaYEhcUAt8pwLniYbtLfHxL/psFF0BykAwCj+s3c6qIpa8yN8keZICWrqtZ70w8g==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [android] + + '@oxfmt/binding-darwin-arm64@0.57.0': + resolution: {integrity: sha512-T+0stuCBqmUVY+aMIvrgXhzGhHO3sD5tNiiEcYqgSdPsnukskQqn2u5qOVD0sv1l7RLdFS5Z/f5Wi9Ktyjr3Eg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [darwin] + + '@oxfmt/binding-darwin-x64@0.57.0': + resolution: {integrity: sha512-O+3JbqWs/mCI2oi4xfhRO2IVPFJNDDEBV8Odo+ZpmsUOeKJfjXoNH7nDmBEQcDgK7NfjDIyE7kRgYSZcTLDO0A==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [darwin] + + '@oxfmt/binding-freebsd-x64@0.57.0': + resolution: {integrity: sha512-pxwhxVC+JkLX9twOQ/8C/vbuOQcMZyKIDmiRDZfO7yITuVcIdZCiLRqqf4QOxb2+8FWrRXzQpm+1DBKcMpHSSQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [freebsd] + + '@oxfmt/binding-linux-arm-gnueabihf@0.57.0': + resolution: {integrity: sha512-pxBU4zH2imB/MDBfth2rOMeVxXUMjRQLCazagwLARIFH3hVlxZJBlM4nSnHXaIHJK4/qezoFCIORN6AY8Mra4A==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [linux] + + '@oxfmt/binding-linux-arm-musleabihf@0.57.0': + resolution: {integrity: sha512-JAprOzt8tycYou36ZgEw14DlRHTiN8qdtKANdV3VZIRIvTI/lh/cX13c9pJ/EnDk2GT3FASH7KvCgQ2AufAifQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [linux] + + '@oxfmt/binding-linux-arm64-gnu@0.57.0': + resolution: {integrity: sha512-ajtjaxSaj9xl4BW7REt+Cef/ttzbAq00Bq4z7JUDZEfgFXdwSjH8K9bF+IcIJzZB9lKqMfQ4eHuSFOvvlvtqOg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@oxfmt/binding-linux-arm64-musl@0.57.0': + resolution: {integrity: sha512-p4Y/+RYk9Bk5WO+zHSUXAClRmZ2fbJCejMuCAsU2HhyME4jqf6Ftt/mJYEwIah1wGCBDYOB7wEGV1x5bCEZ6hA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@oxfmt/binding-linux-ppc64-gnu@0.57.0': + resolution: {integrity: sha512-By6tRALAZsno0F4zedmtG+wdMvJiJmJoXM4d3+A9zHE4HRXLqXITwRH8mgrlcXc5yJM2g2W3riRPwTYdgemZLQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@oxfmt/binding-linux-riscv64-gnu@0.57.0': + resolution: {integrity: sha512-skYeG+RgvyzspqVEBsEprL90OYYZfoVNqB3HcCNR6QDJyXKOzfDRT3zncnHmUaFluIlBHuY23mU1b5WGgR98hA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [riscv64] + os: [linux] + libc: [glibc] + + '@oxfmt/binding-linux-riscv64-musl@0.57.0': + resolution: {integrity: sha512-FFgACrZOXAXUh5KQh2mt1CDOVOZmn+QzHP71wM9QobNwyQvoFfyAeefVUltW83g3sm7LTiH3yfFqLLVUpA5ZFQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [riscv64] + os: [linux] + libc: [musl] + + '@oxfmt/binding-linux-s390x-gnu@0.57.0': + resolution: {integrity: sha512-Nm/BAOfQeFiiKd502mZn/GAVKJwtd0RdCg17G3Wz/WSOIQmDi3+7/SZH4BHn1Ye5KvTVH3ua8WvfwLLycNIuvA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@oxfmt/binding-linux-x64-gnu@0.57.0': + resolution: {integrity: sha512-BiSy5Ku3mQqyxS6YIqAJgd403wEUWvI7kerfzPxc2l/txZVmZM0pSj7oDM+4bGBExowxOi7o73jEam1W0EDTZg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@oxfmt/binding-linux-x64-musl@0.57.0': + resolution: {integrity: sha512-BCRkJiotz5s9afLYD2LuMvzAoDYx9H17E/YbDyu4xK7l4zHDPeny9ErSXL//i/nJyaOwRk08x4b8cgJC00+JDg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [musl] + + '@oxfmt/binding-openharmony-arm64@0.57.0': + resolution: {integrity: sha512-4Oaxe1qrGgXfpCJ1C/ERJ2iCtV2rN1R79ga9fsfyVHfSQRu/hVW780u2KDqZWFZ/iGTHODJji0JemxqFZ63eIQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [openharmony] + + '@oxfmt/binding-win32-arm64-msvc@0.57.0': + resolution: {integrity: sha512-MYLAsDnhdNsSGheLYhWgbk0vfIrlS84iQYun/y21fX6u0jj8iBtYtbpZMdiqYeuf8U12eVPUjVY2xE2NrCfJ0g==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [win32] + + '@oxfmt/binding-win32-ia32-msvc@0.57.0': + resolution: {integrity: sha512-PBwdzZALJY/jcCx2E6is0yu+cuVXeySTDmwuseD+9j0mHqlRNxwlKgsyRTBed/woPeqfVfuXfWjoq4Cx2Zt3Eg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [ia32] + os: [win32] + + '@oxfmt/binding-win32-x64-msvc@0.57.0': + resolution: {integrity: sha512-bQJdH9i4RRfw55jm7+8/xS7GzHLLTbHx4huhrrDxQJaJtbSDbsyOnODvP1ftT7EG0KFKAYO2S+q6AcioXODx8w==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [win32] + + '@oxlint-tsgolint/darwin-arm64@0.24.0': + resolution: {integrity: sha512-C2uMmwK5Bc4ri4ysZ6sA8Rcu+A5zBQTp6ml2u0CLLbRZp4kMFPV3yWk8B5DK9Aw7y9bbjogIm75tUwGLFzlsYQ==} + cpu: [arm64] + os: [darwin] + + '@oxlint-tsgolint/darwin-x64@0.24.0': + resolution: {integrity: sha512-Wgvt/1lRbDxmoNqWQKKcL+UIiqLmdJ+EWLpQa1qzoNVAfNB0PJpa82/8dH1twT/3rSs4zrP5TXPWl4juB71WuQ==} + cpu: [x64] + os: [darwin] + + '@oxlint-tsgolint/linux-arm64@0.24.0': + resolution: {integrity: sha512-PB1rxII7KV83+ASY4sSkXtqvpij6ME66+QCRL49uksi/ofs2Rf/UVboYr095n0Rkbl2wgvlsHGl6DHC361jQUQ==} + cpu: [arm64] + os: [linux] + + '@oxlint-tsgolint/linux-x64@0.24.0': + resolution: {integrity: sha512-xcz3CxKmjTQLREtE/UShh+ruWmm9nAb7UM9zKcD65BStiuYgOakAKkPHl4YS5DztpVcDrE0+HqbOolTlRKYWmw==} + cpu: [x64] + os: [linux] + + '@oxlint-tsgolint/win32-arm64@0.24.0': + resolution: {integrity: sha512-A2i6ZGBec3i20S7RaxkgHc6r3HYtD5Mn7j/mb22NkTz14u0JuudvTu6JggAnbGMcv8+dBKQI//EasxSPJLD8pw==} + cpu: [arm64] + os: [win32] + + '@oxlint-tsgolint/win32-x64@0.24.0': + resolution: {integrity: sha512-0ZbGd9qRB6zs82moekaKdEvncRANq49EAwfNX62JpTS46feXUhKAuoyVDvZMj6Rywejylrmmu79Wo6faYCo4Ew==} + cpu: [x64] + os: [win32] + + '@oxlint/binding-android-arm-eabi@1.72.0': + resolution: {integrity: sha512-zhCmvn+1Mj3UchAc/90i99S0t7jJUsHmFVSPg4UWrjO8b8eaSGwscgO6QAUtvHBstkjQwBttQNswEnAF1mIQdA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [android] + + '@oxlint/binding-android-arm64@1.72.0': + resolution: {integrity: sha512-mtH+aY/ozv1eZoCUC2owjFAtyNBKHpJHygKeEu9zXXnQGW1Q2/qOpvx+I+Lf23+TvTz66F4iiXUbl2cGvoLPCQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [android] + + '@oxlint/binding-darwin-arm64@1.72.0': + resolution: {integrity: sha512-EvnajNPDtfknB3ZieeOOyDTwJn9QXDiwfnF4ZDQqART6RG6hjY4WigQcZdGoK2dkB3e1vrmEzN9aYbQCUkh/gQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [darwin] + + '@oxlint/binding-darwin-x64@1.72.0': + resolution: {integrity: sha512-ZkCdEa/G80A7vEHfeCDz/+L3m33DE73v32mDKhgOIgz8Uwf0DFcK7+uu6qC+7LEhmz5fpOe1osWKyjSNMydFIQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [darwin] + + '@oxlint/binding-freebsd-x64@1.72.0': + resolution: {integrity: sha512-NroXv2vh+sxVY1uya/rM5pjhx1hm8BzlYpx9q67QP0Xhw5MH2bf5GJylpvLEC+781p1Xli/317EoV9AlGwViag==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [freebsd] + + '@oxlint/binding-linux-arm-gnueabihf@1.72.0': + resolution: {integrity: sha512-0NDywYgfj279Ou/BcQuCYSj7NJwBfmWn5qc5uGO/Ny7fUWmXyIpvawqX/8acQlWG6IXelJsJhj+JAy6sjsKj0A==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [linux] + + '@oxlint/binding-linux-arm-musleabihf@1.72.0': + resolution: {integrity: sha512-4vpXB06h65Ezsy4hRyrGjGrfa1SkVPii09yaajiYhmVpgsFiLD+KNxIx/BNAY+XiO+i1yqp9HHdwqM8VTqa5XQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [linux] + + '@oxlint/binding-linux-arm64-gnu@1.72.0': + resolution: {integrity: sha512-immaN4g2ZGFiOkKrvRX9LvzZdd2GkQM5wR+UyzYyUuyhUTXGQ4HKUJH18xp4G8OfhCVaVAJfKZxwE1r8+4hhaQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@oxlint/binding-linux-arm64-musl@1.72.0': + resolution: {integrity: sha512-JGHS9Mnr7iWyyLDxgCv1MhzVpAckgptg00F2gnxt/GD7lQ2SW1BRcxHqhSTaSdDpjWRrBkBxMMh4+Hn3aVtExg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@oxlint/binding-linux-ppc64-gnu@1.72.0': + resolution: {integrity: sha512-AOYgBZqxNshrg83P9v0RYv+m8s10Cqkj4/PxXFDhcS3k7FqsIG5+CxErshZCIN7G8iy4Y+VGfAsuEdar8AcbBg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@oxlint/binding-linux-riscv64-gnu@1.72.0': + resolution: {integrity: sha512-QMybPS5ij3/vrKG67mqzHwW++91sYxK/PPUVi6SBtNCEzW4niS52fVBdXbQ6nou0wWbUPEpx8Sl/ZjtgE3clXA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [riscv64] + os: [linux] + libc: [glibc] + + '@oxlint/binding-linux-riscv64-musl@1.72.0': + resolution: {integrity: sha512-gOc3W7JV0PXRpIL7stUlLe3Wa9Gp0Kdlup87IT3gHDvPKck2xNgMIl/Gs2lldYY2lyXZDC4rWi3hmoLUobkgbQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [riscv64] + os: [linux] + libc: [musl] + + '@oxlint/binding-linux-s390x-gnu@1.72.0': + resolution: {integrity: sha512-rpGxph+FjjHcYI5q6uxB3Az+tnfmEnDbSA8+PK9ZE/VzyUAkvBOMeuY7ZQMhu5mpZH7YQDsTdW6Cx4kV/msc6w==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@oxlint/binding-linux-x64-gnu@1.72.0': + resolution: {integrity: sha512-WND+uhf/Ko13SLqQMWQUgsZuLvYYEvL0ZKgg0tgGYfLqxG7l8Ju123fHDMJyYSDl5E3bUbpFUuii/OvMreFQzw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@oxlint/binding-linux-x64-musl@1.72.0': + resolution: {integrity: sha512-SrpbrUL70nG9vh6zP4/oKHWgLuHquwsr7MW9XOn0olBVgh10Uqr8qscKhQoBGEn6olK/IUpn5GSKcdQ5AjUhGA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [musl] + + '@oxlint/binding-openharmony-arm64@1.72.0': + resolution: {integrity: sha512-qkrsEn6NmgFKr7U/QnezQMb+q/vzAy0Dd9Y95gQGQTyjzDLN+HRZMuM5u70iyH4nBLCfKBzhjMsYCehKay2jyg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [openharmony] + + '@oxlint/binding-win32-arm64-msvc@1.72.0': + resolution: {integrity: sha512-LWR6ZlFZph+KPjXv8opgZsXRDCdrdQe8VL8Cg9zxCoBS73h6znzZpydVgmdnwj8mB9AuSM5jxEgDJDpQkjboeg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [win32] + + '@oxlint/binding-win32-ia32-msvc@1.72.0': + resolution: {integrity: sha512-yt6HEh7IsHvtjRWtmeZRX134eaXKHq5Gnqlf1xBJdJl1JtdoRUEJw3nAxpZoUDS860cX/foKbztO441anVBtVQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [ia32] + os: [win32] + + '@oxlint/binding-win32-x64-msvc@1.72.0': + resolution: {integrity: sha512-b2eKFD2hX7tIwmo/cyH6TDq8vzWRZ2qNHrzoGntUTmq0h3zQh/uX3eTSHCwI8OB/ADQfJCRelLItK8BsxuucDA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [win32] + + '@oxlint/plugins@1.68.0': + resolution: {integrity: sha512-titLmukUt/h8ho7Svlf0xSBjoy2ccZKrXjpXpZCj+v6V4CJccC2KyP45BLSCMx8YIpifMyiDyUptM4+5sruKbQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + '@parcel/watcher-android-arm64@2.5.6': resolution: {integrity: sha512-YQxSS34tPF/6ZG7r/Ih9xy+kP/WwediEUsqmtf0cuCV5TPPKw/PQHRhueUo6JdeFJaqV3pyjm0GdYjZotbRt/A==} engines: {node: '>= 10.0.0'} @@ -1813,6 +2098,16 @@ packages: babel-plugin-react-compiler: optional: true + '@vitest/browser-preview@4.1.9': + resolution: {integrity: sha512-a4/OrkMDb/WUnE4OOB/4FJbK3rYVO7YykqtUgcTKG4p2a0R3XcjPVu7SLRHFBs2+NIYhv5yxp1Lz3dbdGBjIow==} + peerDependencies: + vitest: 4.1.9 + + '@vitest/browser@4.1.9': + resolution: {integrity: sha512-j1BKtWmPcqpMhmx/L9EPLgAJpCb0zKfwoWLmqBbxaogCXHjOwHFSEoHCBfnGtx93xKQwilZ26m+UOsHqHMkRNg==} + peerDependencies: + vitest: 4.1.9 + '@vitest/coverage-v8@4.1.9': resolution: {integrity: sha512-G9/lgqibheLVBDRuya45EbsEXTYcWoSG+TLg7i2axuzx0Eq62eXn+aWXyaVdV5vKvFSWd6ywcX8hA7la9Pvu8g==} peerDependencies: @@ -1856,6 +2151,115 @@ packages: '@vitest/utils@4.1.9': resolution: {integrity: sha512-A51o8ymO5PpqlWNnBP9ZHPXDIpuMtTLlGSjN7la4US+LJzoUMyhwjA5QXlm39JexgwHKW4Xjs8Z2d3dLCXOeuA==} + '@voidzero-dev/vite-plus-core@0.2.2': + resolution: {integrity: sha512-yAbKexF3npOGjg1N5EtXxun+7vdM/0x6QE5jucO/dv0LFhCAIzSN3UvLVCeamJt/Bz3jt7DLqQHEgXXrjy8drA==} + engines: {node: ^20.19.0 || ^22.18.0 || >=24.11.0} + peerDependencies: + '@arethetypeswrong/core': ^0.18.1 + '@types/node': ^20.19.0 || >=22.12.0 + '@vitejs/devtools': ^0.3.0 + esbuild: ^0.27.0 || ^0.28.0 + jiti: '>=1.21.0' + less: ^4.0.0 + publint: ^0.3.8 + sass: ^1.70.0 + sass-embedded: ^1.70.0 + stylus: '>=0.54.8' + sugarss: ^5.0.0 + terser: ^5.16.0 + tsx: ^4.8.1 + typescript: ^5.0.0 || ^6.0.0 + unplugin-unused: ^0.5.0 + unrun: '*' + yaml: ^2.4.2 + peerDependenciesMeta: + '@arethetypeswrong/core': + optional: true + '@types/node': + optional: true + '@vitejs/devtools': + optional: true + esbuild: + optional: true + jiti: + optional: true + less: + optional: true + publint: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + tsx: + optional: true + typescript: + optional: true + unplugin-unused: + optional: true + unrun: + optional: true + yaml: + optional: true + + '@voidzero-dev/vite-plus-darwin-arm64@0.2.2': + resolution: {integrity: sha512-Wy0Shx3Waa2cQZGSrPm0cpO1Y5oNyKyC1jarv12bBcgV+4uoEBKX+ep2Nh7zwjfd8Ja4QMiePE7wciOSXxu8oQ==} + engines: {node: '>=20.0.0'} + cpu: [arm64] + os: [darwin] + + '@voidzero-dev/vite-plus-darwin-x64@0.2.2': + resolution: {integrity: sha512-09xcW67OvsQItVPzmF8UckI+glM3DzyQO3A98deNQ4QtUF7Mt+4/cYKKcLKg2ExRWWXGNDnVG/j7/hiLjZzynw==} + engines: {node: '>=20.0.0'} + cpu: [x64] + os: [darwin] + + '@voidzero-dev/vite-plus-linux-arm64-gnu@0.2.2': + resolution: {integrity: sha512-bR6287UFNwulMiQRhbtXF8GYs9a8EjvefXf+Glm7AzbePUXnamW9cwYIj2j4Dgoje0yC4gA52UePEFjXZnJcjA==} + engines: {node: '>=20.0.0'} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@voidzero-dev/vite-plus-linux-arm64-musl@0.2.2': + resolution: {integrity: sha512-YGtvTHT7qP4c5pZmM4kLL78/d8hj2NS150R92cR2SVOW/l9Ilq5R5WrEiMA4k5Ea3B++IJWZT5MRFI0tW9qlcg==} + engines: {node: '>=20.0.0'} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@voidzero-dev/vite-plus-linux-x64-gnu@0.2.2': + resolution: {integrity: sha512-FvaMI/vsy4PVM+Qd73K+KM8blfCAfaoZaGaGWNrrlMryhyPThXPnHoB1AQcrKbEAWb+z2fc4zLS4sH+8uI65fw==} + engines: {node: '>=20.0.0'} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@voidzero-dev/vite-plus-linux-x64-musl@0.2.2': + resolution: {integrity: sha512-ZsMochHqXqxj2sGTJNJzz3vabppbe4BFgZAjJfsnVzkwR7jv6c5p1BM71LFWxP4qd5LL0TJT7lbeRhALlI44RQ==} + engines: {node: '>=20.0.0'} + cpu: [x64] + os: [linux] + libc: [musl] + + '@voidzero-dev/vite-plus-win32-arm64-msvc@0.2.2': + resolution: {integrity: sha512-noBNyJufux0cf18eDpQLOQUZ1Kybfx9zlr+yQ6gAnxMEsQXSvYqZgWymfRDesBE3G/0XB5bg+AUtWijp3TwVcw==} + engines: {node: '>=20.0.0'} + cpu: [arm64] + os: [win32] + + '@voidzero-dev/vite-plus-win32-x64-msvc@0.2.2': + resolution: {integrity: sha512-+VUui1OIaFX0tqdUAXjmoKVlujEtWVdcsFDw2Jff+D6b4LUTQaOMAaaic8nNdfZL6wEjweRREQLZi/icZAXtNQ==} + engines: {node: '>=20.0.0'} + cpu: [x64] + os: [win32] + '@whatwg-node/disposablestack@0.0.6': resolution: {integrity: sha512-LOtTn+JgJvX8WfBVJtF08TGrdjuFzGJc4mkP8EdDI8ADbvO7kiexYep1o8dwnt0okb0jYclCDXF13xU7Ge4zSw==} engines: {node: '>=18.0.0'} @@ -3572,6 +3976,36 @@ packages: outvariant@1.4.3: resolution: {integrity: sha512-+Sl2UErvtsoajRDKCE5/dBz4DIvHXQQnAxtQTF04OJxY0+DyZXSo5P5Bb7XYWOh81syohlYL24hbDwxedPUJCA==} + oxfmt@0.57.0: + resolution: {integrity: sha512-ZB7Bi+rGDSqmVIo9jwcLyFgjxXvQhDdU+jx+ZrVy6VRiVXK2+CHc4hO3J4dUQjHe7V0ymHB+MDuv5z+NhK07HA==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + peerDependencies: + svelte: ^5.0.0 + vite-plus: '*' + peerDependenciesMeta: + svelte: + optional: true + vite-plus: + optional: true + + oxlint-tsgolint@0.24.0: + resolution: {integrity: sha512-giCk5sEvG02d5tzPmFMX3hem8ndzEEu1xvGYS5OwNfO2WGl6ZVxt5LjE0yiMDoz94INI7XkXwgFAQiydPvVHDw==} + hasBin: true + + oxlint@1.72.0: + resolution: {integrity: sha512-1rhdZIP/EvoI91ABIwNU5Q8+bWf8mjrS5UzIOZld4d4bXxJvtlUhlQvaoTogIGin/qdErMOrwaIJvCSIAKTLhA==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + peerDependencies: + oxlint-tsgolint: '>=0.22.1' + vite-plus: '*' + peerDependenciesMeta: + oxlint-tsgolint: + optional: true + vite-plus: + optional: true + p-cancelable@2.1.1: resolution: {integrity: sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg==} engines: {node: '>=8'} @@ -3689,6 +4123,10 @@ packages: resolution: {integrity: sha512-uysumyrvkUX0rX/dEVqt8gC3sTBzd4zoWfLeS29nb53imdaXVvLINYXTI2GNqzaMuvacNx4uJQ8+b3zXR0pkgQ==} engines: {node: '>=10.4.0'} + pngjs@7.0.0: + resolution: {integrity: sha512-LKWqWJRhstyYo9pGvgor/ivk2w94eSjE3RGVuzLGlr3NmD8bf7RcYGze1mNdEHRP6TRP6rMuDHk5t44hnTRyow==} + engines: {node: '>=14.19.0'} + postcss-calc@8.2.4: resolution: {integrity: sha512-SmWMSJmB8MRnnULldx0lQIyhSNvuDl9HfrZkaqqE/WHAhToYsAvDq+yAsA/kIyINDszOp3Rh0GFoNuH5Ypsm3Q==} peerDependencies: @@ -4431,6 +4869,10 @@ packages: resolution: {integrity: sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==} engines: {node: '>=12.0.0'} + tinypool@2.1.0: + resolution: {integrity: sha512-Pugqs6M0m7Lv1I7FtxN4aoyToKg1C4tu+/381vH35y8oENM/Ai7f7C4StcoK4/+BSw9ebcS8jRiVrORFKCALLw==} + engines: {node: ^20.0.0 || >=22.0.0} + tinyrainbow@3.1.0: resolution: {integrity: sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw==} engines: {node: '>=14.0.0'} @@ -4614,6 +5056,19 @@ packages: vite-plugin-electron-renderer: optional: true + vite-plus@0.2.2: + resolution: {integrity: sha512-bXO3O0F2/uxtvX9Ck0o67stTErH/Zh0GEcCMd9pAh22tTHABCNTDPrRMWVo733e7Ux3h0Y7HanJ7neOV/nid4g==} + engines: {node: ^20.19.0 || ^22.18.0 || >=24.11.0} + hasBin: true + peerDependencies: + '@vitest/browser-playwright': 4.1.9 + '@vitest/browser-webdriverio': 4.1.9 + peerDependenciesMeta: + '@vitest/browser-playwright': + optional: true + '@vitest/browser-webdriverio': + optional: true + vite@8.1.3: resolution: {integrity: sha512-Ds+gBRbj0lwRO2Y5hwnUBdxSwlAve9LeRyU4sNnAr0ewW0gWF0n5bgXgUzbgZ49MV9BVUAQUFYVcDUcilUExMA==} engines: {node: ^20.19.0 || >=22.12.0} @@ -5744,6 +6199,7 @@ snapshots: '@biomejs/cli-linux-x64-musl': 2.5.2 '@biomejs/cli-win32-arm64': 2.5.2 '@biomejs/cli-win32-x64': 2.5.2 + optional: true '@biomejs/cli-darwin-arm64@2.5.2': optional: true @@ -5769,6 +6225,8 @@ snapshots: '@biomejs/cli-win32-x64@2.5.2': optional: true + '@blazediff/core@1.9.1': {} + '@compiled/babel-plugin-strip-runtime@0.40.1': dependencies: '@babel/core': 7.29.7 @@ -6655,8 +7113,144 @@ snapshots: '@open-draft/until@2.1.0': {} + '@oxc-project/runtime@0.138.0': {} + '@oxc-project/types@0.138.0': {} + '@oxfmt/binding-android-arm-eabi@0.57.0': + optional: true + + '@oxfmt/binding-android-arm64@0.57.0': + optional: true + + '@oxfmt/binding-darwin-arm64@0.57.0': + optional: true + + '@oxfmt/binding-darwin-x64@0.57.0': + optional: true + + '@oxfmt/binding-freebsd-x64@0.57.0': + optional: true + + '@oxfmt/binding-linux-arm-gnueabihf@0.57.0': + optional: true + + '@oxfmt/binding-linux-arm-musleabihf@0.57.0': + optional: true + + '@oxfmt/binding-linux-arm64-gnu@0.57.0': + optional: true + + '@oxfmt/binding-linux-arm64-musl@0.57.0': + optional: true + + '@oxfmt/binding-linux-ppc64-gnu@0.57.0': + optional: true + + '@oxfmt/binding-linux-riscv64-gnu@0.57.0': + optional: true + + '@oxfmt/binding-linux-riscv64-musl@0.57.0': + optional: true + + '@oxfmt/binding-linux-s390x-gnu@0.57.0': + optional: true + + '@oxfmt/binding-linux-x64-gnu@0.57.0': + optional: true + + '@oxfmt/binding-linux-x64-musl@0.57.0': + optional: true + + '@oxfmt/binding-openharmony-arm64@0.57.0': + optional: true + + '@oxfmt/binding-win32-arm64-msvc@0.57.0': + optional: true + + '@oxfmt/binding-win32-ia32-msvc@0.57.0': + optional: true + + '@oxfmt/binding-win32-x64-msvc@0.57.0': + optional: true + + '@oxlint-tsgolint/darwin-arm64@0.24.0': + optional: true + + '@oxlint-tsgolint/darwin-x64@0.24.0': + optional: true + + '@oxlint-tsgolint/linux-arm64@0.24.0': + optional: true + + '@oxlint-tsgolint/linux-x64@0.24.0': + optional: true + + '@oxlint-tsgolint/win32-arm64@0.24.0': + optional: true + + '@oxlint-tsgolint/win32-x64@0.24.0': + optional: true + + '@oxlint/binding-android-arm-eabi@1.72.0': + optional: true + + '@oxlint/binding-android-arm64@1.72.0': + optional: true + + '@oxlint/binding-darwin-arm64@1.72.0': + optional: true + + '@oxlint/binding-darwin-x64@1.72.0': + optional: true + + '@oxlint/binding-freebsd-x64@1.72.0': + optional: true + + '@oxlint/binding-linux-arm-gnueabihf@1.72.0': + optional: true + + '@oxlint/binding-linux-arm-musleabihf@1.72.0': + optional: true + + '@oxlint/binding-linux-arm64-gnu@1.72.0': + optional: true + + '@oxlint/binding-linux-arm64-musl@1.72.0': + optional: true + + '@oxlint/binding-linux-ppc64-gnu@1.72.0': + optional: true + + '@oxlint/binding-linux-riscv64-gnu@1.72.0': + optional: true + + '@oxlint/binding-linux-riscv64-musl@1.72.0': + optional: true + + '@oxlint/binding-linux-s390x-gnu@1.72.0': + optional: true + + '@oxlint/binding-linux-x64-gnu@1.72.0': + optional: true + + '@oxlint/binding-linux-x64-musl@1.72.0': + optional: true + + '@oxlint/binding-openharmony-arm64@1.72.0': + optional: true + + '@oxlint/binding-win32-arm64-msvc@1.72.0': + optional: true + + '@oxlint/binding-win32-ia32-msvc@1.72.0': + optional: true + + '@oxlint/binding-win32-x64-msvc@1.72.0': + optional: true + + '@oxlint/plugins@1.68.0': {} + '@parcel/watcher-android-arm64@2.5.6': optional: true @@ -7068,7 +7662,36 @@ snapshots: '@rolldown/pluginutils': 1.0.1 vite: 8.1.3(@types/node@24.13.2)(jiti@2.7.0)(yaml@2.9.0) - '@vitest/coverage-v8@4.1.9(vitest@4.1.9)': + '@vitest/browser-preview@4.1.9(vite@8.1.3(@types/node@24.13.2)(jiti@2.7.0)(yaml@2.9.0))(vitest@4.1.9)': + dependencies: + '@testing-library/dom': 10.4.1 + '@testing-library/user-event': 14.6.1(@testing-library/dom@10.4.1) + '@vitest/browser': 4.1.9(vite@8.1.3(@types/node@24.13.2)(jiti@2.7.0)(yaml@2.9.0))(vitest@4.1.9) + vitest: 4.1.9(@types/node@24.13.2)(@vitest/browser-preview@4.1.9)(@vitest/coverage-v8@4.1.9)(@vitest/ui@4.1.9)(happy-dom@20.10.6)(vite@8.1.3(@types/node@24.13.2)(jiti@2.7.0)(yaml@2.9.0)) + transitivePeerDependencies: + - bufferutil + - msw + - utf-8-validate + - vite + + '@vitest/browser@4.1.9(vite@8.1.3(@types/node@24.13.2)(jiti@2.7.0)(yaml@2.9.0))(vitest@4.1.9)': + dependencies: + '@blazediff/core': 1.9.1 + '@vitest/mocker': 4.1.9(vite@8.1.3(@types/node@24.13.2)(jiti@2.7.0)(yaml@2.9.0)) + '@vitest/utils': 4.1.9 + magic-string: 0.30.21 + pngjs: 7.0.0 + sirv: 3.0.2 + tinyrainbow: 3.1.0 + vitest: 4.1.9(@types/node@24.13.2)(@vitest/browser-preview@4.1.9)(@vitest/coverage-v8@4.1.9)(@vitest/ui@4.1.9)(happy-dom@20.10.6)(vite@8.1.3(@types/node@24.13.2)(jiti@2.7.0)(yaml@2.9.0)) + ws: 8.21.0 + transitivePeerDependencies: + - bufferutil + - msw + - utf-8-validate + - vite + + '@vitest/coverage-v8@4.1.9(@vitest/browser@4.1.9)(vitest@4.1.9)': dependencies: '@bcoe/v8-coverage': 1.0.2 '@vitest/utils': 4.1.9 @@ -7080,7 +7703,9 @@ snapshots: obug: 2.1.3 std-env: 4.1.0 tinyrainbow: 3.1.0 - vitest: 4.1.9(@types/node@24.13.2)(@vitest/coverage-v8@4.1.9)(@vitest/ui@4.1.9)(happy-dom@20.10.6)(vite@8.1.3(@types/node@24.13.2)(jiti@2.7.0)(yaml@2.9.0)) + vitest: 4.1.9(@types/node@24.13.2)(@vitest/browser-preview@4.1.9)(@vitest/coverage-v8@4.1.9)(@vitest/ui@4.1.9)(happy-dom@20.10.6)(vite@8.1.3(@types/node@24.13.2)(jiti@2.7.0)(yaml@2.9.0)) + optionalDependencies: + '@vitest/browser': 4.1.9(vite@8.1.3(@types/node@24.13.2)(jiti@2.7.0)(yaml@2.9.0))(vitest@4.1.9) '@vitest/expect@4.1.9': dependencies: @@ -7126,7 +7751,7 @@ snapshots: sirv: 3.0.2 tinyglobby: 0.2.17 tinyrainbow: 3.1.0 - vitest: 4.1.9(@types/node@24.13.2)(@vitest/coverage-v8@4.1.9)(@vitest/ui@4.1.9)(happy-dom@20.10.6)(vite@8.1.3(@types/node@24.13.2)(jiti@2.7.0)(yaml@2.9.0)) + vitest: 4.1.9(@types/node@24.13.2)(@vitest/browser-preview@4.1.9)(@vitest/coverage-v8@4.1.9)(@vitest/ui@4.1.9)(happy-dom@20.10.6)(vite@8.1.3(@types/node@24.13.2)(jiti@2.7.0)(yaml@2.9.0)) '@vitest/utils@4.1.9': dependencies: @@ -7134,6 +7759,43 @@ snapshots: convert-source-map: 2.0.0 tinyrainbow: 3.1.0 + '@voidzero-dev/vite-plus-core@0.2.2(@types/node@24.13.2)(jiti@2.7.0)(typescript@6.0.3)(yaml@2.9.0)': + dependencies: + '@oxc-project/runtime': 0.138.0 + '@oxc-project/types': 0.138.0 + lightningcss: 1.32.0 + postcss: 8.5.16 + optionalDependencies: + '@types/node': 24.13.2 + fsevents: 2.3.3 + jiti: 2.7.0 + typescript: 6.0.3 + yaml: 2.9.0 + + '@voidzero-dev/vite-plus-darwin-arm64@0.2.2': + optional: true + + '@voidzero-dev/vite-plus-darwin-x64@0.2.2': + optional: true + + '@voidzero-dev/vite-plus-linux-arm64-gnu@0.2.2': + optional: true + + '@voidzero-dev/vite-plus-linux-arm64-musl@0.2.2': + optional: true + + '@voidzero-dev/vite-plus-linux-x64-gnu@0.2.2': + optional: true + + '@voidzero-dev/vite-plus-linux-x64-musl@0.2.2': + optional: true + + '@voidzero-dev/vite-plus-win32-arm64-msvc@0.2.2': + optional: true + + '@voidzero-dev/vite-plus-win32-x64-msvc@0.2.2': + optional: true + '@whatwg-node/disposablestack@0.0.6': dependencies: '@whatwg-node/promise-helpers': 1.3.2 @@ -8908,6 +9570,64 @@ snapshots: outvariant@1.4.3: {} + oxfmt@0.57.0(vite-plus@0.2.2(@types/node@24.13.2)(@vitest/coverage-v8@4.1.9)(@vitest/ui@4.1.9)(happy-dom@20.10.6)(jiti@2.7.0)(typescript@6.0.3)(vite@8.1.3(@types/node@24.13.2)(jiti@2.7.0)(yaml@2.9.0))(yaml@2.9.0)): + dependencies: + tinypool: 2.1.0 + optionalDependencies: + '@oxfmt/binding-android-arm-eabi': 0.57.0 + '@oxfmt/binding-android-arm64': 0.57.0 + '@oxfmt/binding-darwin-arm64': 0.57.0 + '@oxfmt/binding-darwin-x64': 0.57.0 + '@oxfmt/binding-freebsd-x64': 0.57.0 + '@oxfmt/binding-linux-arm-gnueabihf': 0.57.0 + '@oxfmt/binding-linux-arm-musleabihf': 0.57.0 + '@oxfmt/binding-linux-arm64-gnu': 0.57.0 + '@oxfmt/binding-linux-arm64-musl': 0.57.0 + '@oxfmt/binding-linux-ppc64-gnu': 0.57.0 + '@oxfmt/binding-linux-riscv64-gnu': 0.57.0 + '@oxfmt/binding-linux-riscv64-musl': 0.57.0 + '@oxfmt/binding-linux-s390x-gnu': 0.57.0 + '@oxfmt/binding-linux-x64-gnu': 0.57.0 + '@oxfmt/binding-linux-x64-musl': 0.57.0 + '@oxfmt/binding-openharmony-arm64': 0.57.0 + '@oxfmt/binding-win32-arm64-msvc': 0.57.0 + '@oxfmt/binding-win32-ia32-msvc': 0.57.0 + '@oxfmt/binding-win32-x64-msvc': 0.57.0 + vite-plus: 0.2.2(@types/node@24.13.2)(@vitest/coverage-v8@4.1.9)(@vitest/ui@4.1.9)(happy-dom@20.10.6)(jiti@2.7.0)(typescript@6.0.3)(vite@8.1.3(@types/node@24.13.2)(jiti@2.7.0)(yaml@2.9.0))(yaml@2.9.0) + + oxlint-tsgolint@0.24.0: + optionalDependencies: + '@oxlint-tsgolint/darwin-arm64': 0.24.0 + '@oxlint-tsgolint/darwin-x64': 0.24.0 + '@oxlint-tsgolint/linux-arm64': 0.24.0 + '@oxlint-tsgolint/linux-x64': 0.24.0 + '@oxlint-tsgolint/win32-arm64': 0.24.0 + '@oxlint-tsgolint/win32-x64': 0.24.0 + + oxlint@1.72.0(oxlint-tsgolint@0.24.0)(vite-plus@0.2.2(@types/node@24.13.2)(@vitest/coverage-v8@4.1.9)(@vitest/ui@4.1.9)(happy-dom@20.10.6)(jiti@2.7.0)(typescript@6.0.3)(vite@8.1.3(@types/node@24.13.2)(jiti@2.7.0)(yaml@2.9.0))(yaml@2.9.0)): + optionalDependencies: + '@oxlint/binding-android-arm-eabi': 1.72.0 + '@oxlint/binding-android-arm64': 1.72.0 + '@oxlint/binding-darwin-arm64': 1.72.0 + '@oxlint/binding-darwin-x64': 1.72.0 + '@oxlint/binding-freebsd-x64': 1.72.0 + '@oxlint/binding-linux-arm-gnueabihf': 1.72.0 + '@oxlint/binding-linux-arm-musleabihf': 1.72.0 + '@oxlint/binding-linux-arm64-gnu': 1.72.0 + '@oxlint/binding-linux-arm64-musl': 1.72.0 + '@oxlint/binding-linux-ppc64-gnu': 1.72.0 + '@oxlint/binding-linux-riscv64-gnu': 1.72.0 + '@oxlint/binding-linux-riscv64-musl': 1.72.0 + '@oxlint/binding-linux-s390x-gnu': 1.72.0 + '@oxlint/binding-linux-x64-gnu': 1.72.0 + '@oxlint/binding-linux-x64-musl': 1.72.0 + '@oxlint/binding-openharmony-arm64': 1.72.0 + '@oxlint/binding-win32-arm64-msvc': 1.72.0 + '@oxlint/binding-win32-ia32-msvc': 1.72.0 + '@oxlint/binding-win32-x64-msvc': 1.72.0 + oxlint-tsgolint: 0.24.0 + vite-plus: 0.2.2(@types/node@24.13.2)(@vitest/coverage-v8@4.1.9)(@vitest/ui@4.1.9)(happy-dom@20.10.6)(jiti@2.7.0)(typescript@6.0.3)(vite@8.1.3(@types/node@24.13.2)(jiti@2.7.0)(yaml@2.9.0))(yaml@2.9.0) + p-cancelable@2.1.1: {} p-event@4.2.0: @@ -9016,6 +9736,8 @@ snapshots: base64-js: 1.5.1 xmlbuilder: 15.1.1 + pngjs@7.0.0: {} + postcss-calc@8.2.4(postcss@8.5.16): dependencies: postcss: 8.5.16 @@ -9725,6 +10447,8 @@ snapshots: fdir: 6.5.0(picomatch@4.0.5) picomatch: 4.0.5 + tinypool@2.1.0: {} + tinyrainbow@3.1.0: {} title-case@3.0.3: @@ -9845,7 +10569,7 @@ snapshots: util-deprecate@1.0.2: {} - vite-plugin-checker@0.14.4(@biomejs/biome@2.5.2)(typescript@6.0.3)(vite@8.1.3(@types/node@24.13.2)(jiti@2.7.0)(yaml@2.9.0)): + vite-plugin-checker@0.14.4(@biomejs/biome@2.5.2)(oxlint@1.72.0(oxlint-tsgolint@0.24.0)(vite-plus@0.2.2(@types/node@24.13.2)(@vitest/coverage-v8@4.1.9)(@vitest/ui@4.1.9)(happy-dom@20.10.6)(jiti@2.7.0)(typescript@6.0.3)(vite@8.1.3(@types/node@24.13.2)(jiti@2.7.0)(yaml@2.9.0))(yaml@2.9.0)))(typescript@6.0.3)(vite@8.1.3(@types/node@24.13.2)(jiti@2.7.0)(yaml@2.9.0)): dependencies: '@babel/code-frame': 7.29.7 chokidar: 5.0.0 @@ -9857,12 +10581,71 @@ snapshots: vite: 8.1.3(@types/node@24.13.2)(jiti@2.7.0)(yaml@2.9.0) optionalDependencies: '@biomejs/biome': 2.5.2 + oxlint: 1.72.0(oxlint-tsgolint@0.24.0)(vite-plus@0.2.2(@types/node@24.13.2)(@vitest/coverage-v8@4.1.9)(@vitest/ui@4.1.9)(happy-dom@20.10.6)(jiti@2.7.0)(typescript@6.0.3)(vite@8.1.3(@types/node@24.13.2)(jiti@2.7.0)(yaml@2.9.0))(yaml@2.9.0)) typescript: 6.0.3 vite-plugin-electron@1.1.0: dependencies: local-pkg: 1.2.1 + vite-plus@0.2.2(@types/node@24.13.2)(@vitest/coverage-v8@4.1.9)(@vitest/ui@4.1.9)(happy-dom@20.10.6)(jiti@2.7.0)(typescript@6.0.3)(vite@8.1.3(@types/node@24.13.2)(jiti@2.7.0)(yaml@2.9.0))(yaml@2.9.0): + dependencies: + '@oxc-project/types': 0.138.0 + '@oxlint/plugins': 1.68.0 + '@vitest/browser': 4.1.9(vite@8.1.3(@types/node@24.13.2)(jiti@2.7.0)(yaml@2.9.0))(vitest@4.1.9) + '@vitest/browser-preview': 4.1.9(vite@8.1.3(@types/node@24.13.2)(jiti@2.7.0)(yaml@2.9.0))(vitest@4.1.9) + '@vitest/expect': 4.1.9 + '@vitest/mocker': 4.1.9(vite@8.1.3(@types/node@24.13.2)(jiti@2.7.0)(yaml@2.9.0)) + '@vitest/pretty-format': 4.1.9 + '@vitest/runner': 4.1.9 + '@vitest/snapshot': 4.1.9 + '@vitest/spy': 4.1.9 + '@vitest/utils': 4.1.9 + '@voidzero-dev/vite-plus-core': 0.2.2(@types/node@24.13.2)(jiti@2.7.0)(typescript@6.0.3)(yaml@2.9.0) + oxfmt: 0.57.0(vite-plus@0.2.2(@types/node@24.13.2)(@vitest/coverage-v8@4.1.9)(@vitest/ui@4.1.9)(happy-dom@20.10.6)(jiti@2.7.0)(typescript@6.0.3)(vite@8.1.3(@types/node@24.13.2)(jiti@2.7.0)(yaml@2.9.0))(yaml@2.9.0)) + oxlint: 1.72.0(oxlint-tsgolint@0.24.0)(vite-plus@0.2.2(@types/node@24.13.2)(@vitest/coverage-v8@4.1.9)(@vitest/ui@4.1.9)(happy-dom@20.10.6)(jiti@2.7.0)(typescript@6.0.3)(vite@8.1.3(@types/node@24.13.2)(jiti@2.7.0)(yaml@2.9.0))(yaml@2.9.0)) + oxlint-tsgolint: 0.24.0 + vitest: 4.1.9(@types/node@24.13.2)(@vitest/browser-preview@4.1.9)(@vitest/coverage-v8@4.1.9)(@vitest/ui@4.1.9)(happy-dom@20.10.6)(vite@8.1.3(@types/node@24.13.2)(jiti@2.7.0)(yaml@2.9.0)) + optionalDependencies: + '@voidzero-dev/vite-plus-darwin-arm64': 0.2.2 + '@voidzero-dev/vite-plus-darwin-x64': 0.2.2 + '@voidzero-dev/vite-plus-linux-arm64-gnu': 0.2.2 + '@voidzero-dev/vite-plus-linux-arm64-musl': 0.2.2 + '@voidzero-dev/vite-plus-linux-x64-gnu': 0.2.2 + '@voidzero-dev/vite-plus-linux-x64-musl': 0.2.2 + '@voidzero-dev/vite-plus-win32-arm64-msvc': 0.2.2 + '@voidzero-dev/vite-plus-win32-x64-msvc': 0.2.2 + transitivePeerDependencies: + - '@arethetypeswrong/core' + - '@edge-runtime/vm' + - '@opentelemetry/api' + - '@types/node' + - '@vitejs/devtools' + - '@vitest/coverage-istanbul' + - '@vitest/coverage-v8' + - '@vitest/ui' + - bufferutil + - esbuild + - happy-dom + - jiti + - jsdom + - less + - msw + - publint + - sass + - sass-embedded + - stylus + - sugarss + - svelte + - terser + - tsx + - typescript + - unplugin-unused + - unrun + - utf-8-validate + - vite + - yaml + vite@8.1.3(@types/node@24.13.2)(jiti@2.7.0)(yaml@2.9.0): dependencies: lightningcss: 1.32.0 @@ -9876,7 +10659,7 @@ snapshots: jiti: 2.7.0 yaml: 2.9.0 - vitest@4.1.9(@types/node@24.13.2)(@vitest/coverage-v8@4.1.9)(@vitest/ui@4.1.9)(happy-dom@20.10.6)(vite@8.1.3(@types/node@24.13.2)(jiti@2.7.0)(yaml@2.9.0)): + vitest@4.1.9(@types/node@24.13.2)(@vitest/browser-preview@4.1.9)(@vitest/coverage-v8@4.1.9)(@vitest/ui@4.1.9)(happy-dom@20.10.6)(vite@8.1.3(@types/node@24.13.2)(jiti@2.7.0)(yaml@2.9.0)): dependencies: '@vitest/expect': 4.1.9 '@vitest/mocker': 4.1.9(vite@8.1.3(@types/node@24.13.2)(jiti@2.7.0)(yaml@2.9.0)) @@ -9900,7 +10683,8 @@ snapshots: why-is-node-running: 2.3.0 optionalDependencies: '@types/node': 24.13.2 - '@vitest/coverage-v8': 4.1.9(vitest@4.1.9) + '@vitest/browser-preview': 4.1.9(vite@8.1.3(@types/node@24.13.2)(jiti@2.7.0)(yaml@2.9.0))(vitest@4.1.9) + '@vitest/coverage-v8': 4.1.9(@vitest/browser@4.1.9)(vitest@4.1.9) '@vitest/ui': 4.1.9(vitest@4.1.9) happy-dom: 20.10.6 transitivePeerDependencies: diff --git a/scripts/afterPack.js b/scripts/afterPack.js index 043080cae..4a7f82d45 100644 --- a/scripts/afterPack.js +++ b/scripts/afterPack.js @@ -5,7 +5,7 @@ const builderConfig = require('../electron-builder'); const electronLanguages = builderConfig.electronLanguages; function logAfterPackProgress(msg) { - // biome-ignore lint/suspicious/noConsole: log notarizing progress + // oxlint-disable-next-line no-console -- log notarizing progress console.log(` β€’ [afterPack]: ${msg}`); } @@ -46,9 +46,7 @@ const removeUnusedLocales = (appOutDir, appName) => { ); // Get all locale directories - const allLocales = fs - .readdirSync(resourcesPath) - .filter((file) => file.endsWith('.lproj')); + const allLocales = fs.readdirSync(resourcesPath).filter((file) => file.endsWith('.lproj')); const langLocales = electronLanguages.map((lang) => `${lang}.lproj`); diff --git a/scripts/afterSign.js b/scripts/afterSign.js index 11436d85a..6b95e0b64 100644 --- a/scripts/afterSign.js +++ b/scripts/afterSign.js @@ -1,7 +1,7 @@ const { notarize } = require('@electron/notarize'); function logAfterSignProgress(msg) { - // biome-ignore lint/suspicious/noConsole: log notarizing progress + // oxlint-disable-next-line no-console -- log notarizing progress console.log(` β€’ [afterSign]: ${msg}`); } @@ -16,9 +16,7 @@ const afterSign = async (context) => { const shouldNotarize = process.env.NOTARIZE === 'true'; if (!shouldNotarize) { - logAfterSignProgress( - 'skipping notarize step as NOTARIZE env flag was not set', - ); + logAfterSignProgress('skipping notarize step as NOTARIZE env flag was not set'); return; } diff --git a/src/main/config.ts b/src/main/config.ts index 84501bc49..50a3d9a4e 100644 --- a/src/main/config.ts +++ b/src/main/config.ts @@ -21,18 +21,12 @@ export const Paths = { get notificationSound(): string { return pathToFileURL( - path.resolve( - __dirname, - 'assets', - 'sounds', - APPLICATION.NOTIFICATION_SOUND, - ), + path.resolve(__dirname, 'assets', 'sounds', APPLICATION.NOTIFICATION_SOUND), ).href; }, get twemojiFolder(): string { - return pathToFileURL(path.resolve(__dirname, 'assets', 'images', 'twemoji')) - .href; + return pathToFileURL(path.resolve(__dirname, 'assets', 'images', 'twemoji')).href; }, }; diff --git a/src/main/events.test.ts b/src/main/events.test.ts index 07c86ba53..774520054 100644 --- a/src/main/events.test.ts +++ b/src/main/events.test.ts @@ -39,11 +39,7 @@ describe('main/events', () => { const sendMock = vi.fn(); const mb: MockMenubar = { window: { webContents: { send: sendMock } } }; - sendRendererEvent( - mb as unknown as Menubar, - EVENTS.UPDATE_ICON_TITLE, - 'title', - ); + sendRendererEvent(mb as unknown as Menubar, EVENTS.UPDATE_ICON_TITLE, 'title'); expect(sendMock).toHaveBeenCalledWith(EVENTS.UPDATE_ICON_TITLE, 'title'); }); diff --git a/src/main/events.ts b/src/main/events.ts index 8e8ee5780..3f248ccc5 100644 --- a/src/main/events.ts +++ b/src/main/events.ts @@ -1,12 +1,7 @@ import { ipcMain } from 'electron'; import type { Menubar } from 'electron-menubar'; -import type { - EventArgs, - EventRequest, - EventResponse, - EventType, -} from '../shared/events'; +import type { EventArgs, EventRequest, EventResponse, EventType } from '../shared/events'; /** * Register a fire-and-forget IPC listener on the main process (ipcMain.on). diff --git a/src/main/handlers/analytics.test.ts b/src/main/handlers/analytics.test.ts index c3069a6ae..2ae843734 100644 --- a/src/main/handlers/analytics.test.ts +++ b/src/main/handlers/analytics.test.ts @@ -49,10 +49,7 @@ describe('main/handlers/analytics.ts', () => { await initializeAnalytics(); expect(initializeMock).toHaveBeenCalledWith('test-key-123'); - expect(logInfoMock).toHaveBeenCalledWith( - 'analytics', - 'Aptabase initialized successfully', - ); + expect(logInfoMock).toHaveBeenCalledWith('analytics', 'Aptabase initialized successfully'); expect(logErrorMock).not.toHaveBeenCalled(); }); @@ -81,9 +78,7 @@ describe('main/handlers/analytics.ts', () => { it('registers the APTABASE_TRACK_EVENT handler', () => { registerAnalyticsHandlers(); - const registeredEvents = onMock.mock.calls.map( - (call: [string]) => call[0], - ); + const registeredEvents = onMock.mock.calls.map((call: [string]) => call[0]); expect(registeredEvents).toContain(EVENTS.APTABASE_TRACK_EVENT); }); diff --git a/src/main/handlers/app.test.ts b/src/main/handlers/app.test.ts index f2901f234..c75233e66 100644 --- a/src/main/handlers/app.test.ts +++ b/src/main/handlers/app.test.ts @@ -43,12 +43,8 @@ describe('main/handlers/app.ts', () => { it('registers expected app IPC event handlers', () => { registerAppHandlers(menubar); - const registeredHandlers = handleMock.mock.calls.map( - (call: [string]) => call[0], - ); - const registeredEvents = onMock.mock.calls.map( - (call: [string]) => call[0], - ); + const registeredHandlers = handleMock.mock.calls.map((call: [string]) => call[0]); + const registeredEvents = onMock.mock.calls.map((call: [string]) => call[0]); expect(registeredHandlers).toContain(EVENTS.VERSION); expect(registeredHandlers).toContain(EVENTS.NOTIFICATION_SOUND_PATH); diff --git a/src/main/handlers/storage.test.ts b/src/main/handlers/storage.test.ts index e59ff4102..f9af9b8da 100644 --- a/src/main/handlers/storage.test.ts +++ b/src/main/handlers/storage.test.ts @@ -28,9 +28,7 @@ describe('main/handlers/storage.ts', () => { it('registers expected storage IPC event handlers', () => { registerStorageHandlers(); - const registeredHandlers = handleMock.mock.calls.map( - (call: [string]) => call[0], - ); + const registeredHandlers = handleMock.mock.calls.map((call: [string]) => call[0]); expect(registeredHandlers).toContain(EVENTS.SAFE_STORAGE_ENCRYPT); expect(registeredHandlers).toContain(EVENTS.SAFE_STORAGE_DECRYPT); diff --git a/src/main/handlers/system.test.ts b/src/main/handlers/system.test.ts index 3d73c60de..d33583a06 100644 --- a/src/main/handlers/system.test.ts +++ b/src/main/handlers/system.test.ts @@ -50,9 +50,7 @@ describe('main/handlers/system.ts', () => { it('registers expected system IPC event handlers', () => { registerSystemHandlers(menubar); - const registeredEvents = onMock.mock.calls.map( - (call: [string]) => call[0], - ); + const registeredEvents = onMock.mock.calls.map((call: [string]) => call[0]); expect(registeredEvents).toContain(EVENTS.OPEN_EXTERNAL); expect(registeredEvents).toContain(EVENTS.UPDATE_KEYBOARD_SHORTCUT); diff --git a/src/main/handlers/system.ts b/src/main/handlers/system.ts index e2b7bef3a..8e87f76a5 100644 --- a/src/main/handlers/system.ts +++ b/src/main/handlers/system.ts @@ -30,10 +30,7 @@ export function registerSystemHandlers(mb: Menubar): void { */ powerMonitor.on('unlock-screen', () => { sendRendererEvent(mb, EVENTS.SYSTEM_WAKE); - logInfo( - 'power-monitor', - 'unlock-screen event triggered, will refetch data', - ); + logInfo('power-monitor', 'unlock-screen event triggered, will refetch data'); }); /** diff --git a/src/main/handlers/tray.test.ts b/src/main/handlers/tray.test.ts index 3a2ff1a1b..abb6a57a8 100644 --- a/src/main/handlers/tray.test.ts +++ b/src/main/handlers/tray.test.ts @@ -34,9 +34,7 @@ describe('main/handlers/tray.ts', () => { it('registers expected tray IPC event handlers', () => { registerTrayHandlers(menubar); - const registeredEvents = onMock.mock.calls.map( - (call: [string]) => call[0], - ); + const registeredEvents = onMock.mock.calls.map((call: [string]) => call[0]); expect(registeredEvents).toContain(EVENTS.UPDATE_ICON_COLOR); expect(registeredEvents).toContain(EVENTS.UPDATE_ICON_TITLE); @@ -52,15 +50,11 @@ describe('main/handlers/tray.ts', () => { }; it('returns offline icon when appState is offline', () => { - expect(selectTrayIcon({ ...base, appState: 'offline' })).toBe( - TrayIcons.offline, - ); + expect(selectTrayIcon({ ...base, appState: 'offline' })).toBe(TrayIcons.offline); }); it('returns error icon when appState is error', () => { - expect(selectTrayIcon({ ...base, appState: 'error' })).toBe( - TrayIcons.error, - ); + expect(selectTrayIcon({ ...base, appState: 'error' })).toBe(TrayIcons.error); }); it('returns active icon when there are notifications and unreadIconVariant is active', () => { @@ -95,9 +89,7 @@ describe('main/handlers/tray.ts', () => { }); it('returns idle icon when there are no notifications', () => { - expect(selectTrayIcon({ ...base, notificationsCount: 0 })).toBe( - TrayIcons.idle, - ); + expect(selectTrayIcon({ ...base, notificationsCount: 0 })).toBe(TrayIcons.idle); }); it('returns idleAlternate icon when there are no notifications and idleIconVariant is alternative', () => { diff --git a/src/main/handlers/tray.ts b/src/main/handlers/tray.ts index 14618c76b..766872ee7 100644 --- a/src/main/handlers/tray.ts +++ b/src/main/handlers/tray.ts @@ -12,8 +12,7 @@ import { TrayIcons } from '../icons'; * @returns The icon path to display. */ export function selectTrayIcon(state: ITrayColorUpdate): string { - const { notificationsCount, appState, idleIconVariant, unreadIconVariant } = - state; + const { notificationsCount, appState, idleIconVariant, unreadIconVariant } = state; if (appState === 'error') { return TrayIcons.error; @@ -27,9 +26,7 @@ export function selectTrayIcon(state: ITrayColorUpdate): string { return TrayIcons.active; } - return idleIconVariant === 'alternative' - ? TrayIcons.idleAlternate - : TrayIcons.idle; + return idleIconVariant === 'alternative' ? TrayIcons.idleAlternate : TrayIcons.idle; } /** diff --git a/src/main/icons.test.ts b/src/main/icons.test.ts index a7093f2f9..edb0f53be 100644 --- a/src/main/icons.test.ts +++ b/src/main/icons.test.ts @@ -6,9 +6,7 @@ describe('main/icons.ts', () => { expect(TrayIcons.idle).toContain('assets/images/tray-idleTemplate.png'); - expect(TrayIcons.idleAlternate).toContain( - 'assets/images/tray-idle-white.png', - ); + expect(TrayIcons.idleAlternate).toContain('assets/images/tray-idle-white.png'); expect(TrayIcons.error).toContain('assets/images/tray-error.png'); diff --git a/src/main/index.ts b/src/main/index.ts index 3a40cf9b3..324d8bfb8 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -12,11 +12,7 @@ import { registerTrayHandlers, } from './handlers'; import { TrayIcons } from './icons'; -import { - configureWindowEvents, - initializeAppLifecycle, - onFirstRunMaybe, -} from './lifecycle'; +import { configureWindowEvents, initializeAppLifecycle, onFirstRunMaybe } from './lifecycle'; import MenuBuilder from './menu'; import AppUpdater from './updater'; diff --git a/src/main/lifecycle/startup.test.ts b/src/main/lifecycle/startup.test.ts index 1aa2967cd..1c1b200bd 100644 --- a/src/main/lifecycle/startup.test.ts +++ b/src/main/lifecycle/startup.test.ts @@ -70,8 +70,7 @@ describe('main/lifecycle/startup.ts', () => { initializeAppLifecycle(mb as unknown as Menubar, contextMenu); - const readyHandler = (mb.on as unknown as ReturnType).mock - .calls[0]?.[1]; + const readyHandler = (mb.on as unknown as ReturnType).mock.calls[0]?.[1]; expect(readyHandler).toBeDefined(); readyHandler?.(); diff --git a/src/main/lifecycle/startup.ts b/src/main/lifecycle/startup.ts index ae5b9877c..03201902d 100644 --- a/src/main/lifecycle/startup.ts +++ b/src/main/lifecycle/startup.ts @@ -14,10 +14,7 @@ import { sendRendererEvent } from '../events'; * @param mb - The menubar instance to attach lifecycle events to. * @param contextMenu - The tray context menu to pop up on right-click. */ -export function initializeAppLifecycle( - mb: Menubar, - contextMenu: Electron.Menu, -): void { +export function initializeAppLifecycle(mb: Menubar, contextMenu: Electron.Menu): void { mb.on('ready', () => { mb.app.setAppUserModelId(APPLICATION.ID); mb.tray.setToolTip(APPLICATION.NAME); diff --git a/src/main/lifecycle/window.test.ts b/src/main/lifecycle/window.test.ts index a7190b8f3..aaaa2df25 100644 --- a/src/main/lifecycle/window.test.ts +++ b/src/main/lifecycle/window.test.ts @@ -44,15 +44,10 @@ const findWindowHandler = ( ): ((event: { preventDefault: () => void }) => void) | undefined => { const onMock = menubar.window?.on as ReturnType; const call = onMock.mock.calls.find(([name]) => name === eventName); - return call?.[1] as - | ((event: { preventDefault: () => void }) => void) - | undefined; + return call?.[1] as ((event: { preventDefault: () => void }) => void) | undefined; }; -const findWebContentsHandler = ( - menubar: Menubar, - eventName: string, -): (() => void) | undefined => { +const findWebContentsHandler = (menubar: Menubar, eventName: string): (() => void) | undefined => { const onMock = menubar.window?.webContents.on as ReturnType; const call = onMock.mock.calls.find(([name]) => name === eventName); return call?.[1] as (() => void) | undefined; @@ -72,9 +67,7 @@ describe('main/lifecycle/window.ts', () => { hideWindow: vi.fn(), recenterOnTray: vi.fn(), tray: { - getBounds: vi - .fn() - .mockReturnValue({ x: 100, y: 100, width: 22, height: 22 }), + getBounds: vi.fn().mockReturnValue({ x: 100, y: 100, width: 22, height: 22 }), }, window: { setSize: vi.fn(), @@ -124,10 +117,7 @@ describe('main/lifecycle/window.ts', () => { it('does not register a close, before-input-event, or its own escape handler (library owns those)', () => { configureWindowEvents(menubar, menuBuilder); - expect(menubar.window?.on).not.toHaveBeenCalledWith( - 'close', - expect.any(Function), - ); + expect(menubar.window?.on).not.toHaveBeenCalledWith('close', expect.any(Function)); expect(menubar.window?.webContents.on).not.toHaveBeenCalledWith( 'before-input-event', expect.any(Function), @@ -138,10 +128,7 @@ describe('main/lifecycle/window.ts', () => { configureWindowEvents(menubar, menuBuilder); expect(appOnMock).toHaveBeenCalledWith('before-quit', expect.any(Function)); - expect(appOnMock).toHaveBeenCalledWith( - 'window-all-closed', - expect.any(Function), - ); + expect(appOnMock).toHaveBeenCalledWith('window-all-closed', expect.any(Function)); }); describe('window visibility forwarding', () => { @@ -173,9 +160,7 @@ describe('main/lifecycle/window.ts', () => { it('skips the call when the window is destroyed', () => { // oxlint-disable-next-line no-unsafe-optional-chaining -- window is guaranteed defined in this test - (menubar.window?.isDestroyed as ReturnType).mockReturnValue( - true, - ); + (menubar.window?.isDestroyed as ReturnType).mockReturnValue(true); applyKeepWindowOnBlur(menubar, true); diff --git a/src/main/lifecycle/window.ts b/src/main/lifecycle/window.ts index 17019667b..45812175c 100644 --- a/src/main/lifecycle/window.ts +++ b/src/main/lifecycle/window.ts @@ -47,10 +47,7 @@ export function applyKeepWindowOnBlur(mb: Menubar, value: boolean): void { * @param menuBuilder - The menu builder used to keep the Show / Hide tray * menu items in sync with window visibility. */ -export function configureWindowEvents( - mb: Menubar, - menuBuilder: MenuBuilder, -): void { +export function configureWindowEvents(mb: Menubar, menuBuilder: MenuBuilder): void { const win = mb.window; if (!win) { return; diff --git a/src/main/menu.test.ts b/src/main/menu.test.ts index 4bfd0ae1a..b1f377355 100644 --- a/src/main/menu.test.ts +++ b/src/main/menu.test.ts @@ -84,9 +84,7 @@ describe('main/menu.ts', () => { /** Helper: build menu & return template (first arg passed to buildFromTemplate) */ const buildAndGetTemplate = () => { menuBuilder.buildMenu(); - return (Menu.buildFromTemplate as Mock).mock.calls.slice( - -1, - )[0][0] as TemplateItem[]; + return (Menu.buildFromTemplate as Mock).mock.calls.slice(-1)[0][0] as TemplateItem[]; }; beforeEach(() => { @@ -117,14 +115,12 @@ describe('main/menu.ts', () => { it('should enable menu item', () => { menuBuilder.setCheckForUpdatesMenuEnabled(true); - // biome-ignore lint/complexity/useLiteralKeys: This is a test expect(menuBuilder['checkForUpdatesMenuItem'].enabled).toBe(true); }); it('should disable menu item', () => { menuBuilder.setCheckForUpdatesMenuEnabled(false); - // biome-ignore lint/complexity/useLiteralKeys: This is a test expect(menuBuilder['checkForUpdatesMenuItem'].enabled).toBe(false); }); }); @@ -142,14 +138,12 @@ describe('main/menu.ts', () => { it('should show menu item', () => { menuBuilder.setNoUpdateAvailableMenuVisibility(true); - // biome-ignore lint/complexity/useLiteralKeys: This is a test expect(menuBuilder['noUpdateAvailableMenuItem'].visible).toBe(true); }); it('should hide menu item', () => { menuBuilder.setNoUpdateAvailableMenuVisibility(false); - // biome-ignore lint/complexity/useLiteralKeys: This is a test expect(menuBuilder['noUpdateAvailableMenuItem'].visible).toBe(false); }); }); @@ -167,14 +161,12 @@ describe('main/menu.ts', () => { it('should show menu item', () => { menuBuilder.setUpdateAvailableMenuVisibility(true); - // biome-ignore lint/complexity/useLiteralKeys: This is a test expect(menuBuilder['updateAvailableMenuItem'].visible).toBe(true); }); it('should hide menu item', () => { menuBuilder.setUpdateAvailableMenuVisibility(false); - // biome-ignore lint/complexity/useLiteralKeys: This is a test expect(menuBuilder['updateAvailableMenuItem'].visible).toBe(false); }); }); @@ -193,14 +185,12 @@ describe('main/menu.ts', () => { it('should show menu item', () => { menuBuilder.setUpdateReadyForInstallMenuVisibility(true); - // biome-ignore lint/complexity/useLiteralKeys: This is a test expect(menuBuilder['updateReadyForInstallMenuItem'].visible).toBe(true); }); it('should hide menu item', () => { menuBuilder.setUpdateReadyForInstallMenuVisibility(false); - // biome-ignore lint/complexity/useLiteralKeys: This is a test expect(menuBuilder['updateReadyForInstallMenuItem'].visible).toBe(false); }); }); @@ -217,9 +207,7 @@ describe('main/menu.ts', () => { it('setWindowVisibility(true) shows hide item, hides show item', () => { menuBuilder.setWindowVisibility(true); - // biome-ignore lint/complexity/useLiteralKeys: This is a test expect(menuBuilder['showWindowMenuItem'].visible).toBe(false); - // biome-ignore lint/complexity/useLiteralKeys: This is a test expect(menuBuilder['hideWindowMenuItem'].visible).toBe(true); }); @@ -227,9 +215,7 @@ describe('main/menu.ts', () => { menuBuilder.setWindowVisibility(true); menuBuilder.setWindowVisibility(false); - // biome-ignore lint/complexity/useLiteralKeys: This is a test expect(menuBuilder['showWindowMenuItem'].visible).toBe(true); - // biome-ignore lint/complexity/useLiteralKeys: This is a test expect(menuBuilder['hideWindowMenuItem'].visible).toBe(false); }); @@ -262,13 +248,10 @@ describe('main/menu.ts', () => { it('developer submenu click actions execute expected functions', () => { const template = buildAndGetTemplate(); - const devEntry = template.find( - (item) => item?.label === 'Developer', - ) as TemplateItem; + const devEntry = template.find((item) => item?.label === 'Developer') as TemplateItem; expect(devEntry).toBeDefined(); const submenu = devEntry.submenu; - const clickByLabel = (label: string) => - submenu.find((i) => i.label === label)?.click?.(); + const clickByLabel = (label: string) => submenu.find((i) => i.label === label)?.click?.(); clickByLabel('Take Screenshot'); expect(takeScreenshot).toHaveBeenCalledWith(menubar); @@ -303,9 +286,7 @@ describe('main/menu.ts', () => { it('developer submenu includes expected static accelerators', () => { const template = buildAndGetTemplate(); - const devEntry = template.find( - (item) => item?.label === 'Developer', - ) as TemplateItem; + const devEntry = template.find((item) => item?.label === 'Developer') as TemplateItem; const reloadItem = devEntry.submenu.find((i) => i.role === 'reload'); expect(reloadItem?.accelerator).toBe('CommandOrControl+R'); @@ -329,12 +310,8 @@ describe('main/menu.ts', () => { const template = (Menu.buildFromTemplate as Mock).mock.calls.slice( -1, )[0][0] as TemplateItem[]; - const devEntry = template.find( - (i) => i?.label === 'Developer', - ) as TemplateItem; - const toggleItem = devEntry.submenu?.find( - (i) => i.role === 'toggleDevTools', - ); + const devEntry = template.find((i) => i?.label === 'Developer') as TemplateItem; + const toggleItem = devEntry.submenu?.find((i) => i.role === 'toggleDevTools'); expect(toggleItem?.accelerator).toBe('Alt+Cmd+I'); }); @@ -351,12 +328,8 @@ describe('main/menu.ts', () => { const template = (Menu.buildFromTemplate as Mock).mock.calls.slice( -1, )[0][0] as TemplateItem[]; - const devEntry = template.find( - (i) => i?.label === 'Developer', - ) as TemplateItem; - const toggleItem = devEntry.submenu?.find( - (i) => i.role === 'toggleDevTools', - ); + const devEntry = template.find((i) => i?.label === 'Developer') as TemplateItem; + const toggleItem = devEntry.submenu?.find((i) => i.role === 'toggleDevTools'); expect(toggleItem?.accelerator).toBe('Ctrl+Shift+I'); }); diff --git a/src/main/updater.test.ts b/src/main/updater.test.ts index e8ddf9acc..b0c4755ee 100644 --- a/src/main/updater.test.ts +++ b/src/main/updater.test.ts @@ -110,12 +110,8 @@ describe('main/updater.ts', () => { }), ); expect(autoUpdater.quitAndInstall).not.toHaveBeenCalled(); - expect(menuBuilder.setUpdateAvailableMenuVisibility).toHaveBeenCalledWith( - false, - ); - expect( - menuBuilder.setUpdateReadyForInstallMenuVisibility, - ).toHaveBeenCalledWith(true); + expect(menuBuilder.setUpdateAvailableMenuVisibility).toHaveBeenCalledWith(false); + expect(menuBuilder.setUpdateReadyForInstallMenuVisibility).toHaveBeenCalledWith(true); }); it('invokes quitAndInstall when user clicks Restart', async () => { @@ -153,12 +149,8 @@ describe('main/updater.ts', () => { emit('checking-for-update'); - expect(menuBuilder.setCheckForUpdatesMenuEnabled).toHaveBeenCalledWith( - false, - ); - expect( - menuBuilder.setNoUpdateAvailableMenuVisibility, - ).toHaveBeenCalledWith(false); + expect(menuBuilder.setCheckForUpdatesMenuEnabled).toHaveBeenCalledWith(false); + expect(menuBuilder.setNoUpdateAvailableMenuVisibility).toHaveBeenCalledWith(false); }); it('handles update-available', async () => { @@ -166,9 +158,7 @@ describe('main/updater.ts', () => { emit('update-available'); - expect(menuBuilder.setUpdateAvailableMenuVisibility).toHaveBeenCalledWith( - true, - ); + expect(menuBuilder.setUpdateAvailableMenuVisibility).toHaveBeenCalledWith(true); expect(menubar.tray.setToolTip).toHaveBeenCalledWith( expect.stringContaining('A new update is available'), ); @@ -179,9 +169,7 @@ describe('main/updater.ts', () => { emit('download-progress', { percent: 12.3456 }); - expect(menubar.tray.setToolTip).toHaveBeenCalledWith( - expect.stringContaining('12.35%'), - ); + expect(menubar.tray.setToolTip).toHaveBeenCalledWith(expect.stringContaining('12.35%')); }); it('handles update-not-available', async () => { @@ -189,18 +177,10 @@ describe('main/updater.ts', () => { emit('update-not-available'); - expect(menuBuilder.setCheckForUpdatesMenuEnabled).toHaveBeenCalledWith( - true, - ); - expect( - menuBuilder.setNoUpdateAvailableMenuVisibility, - ).toHaveBeenCalledWith(true); - expect(menuBuilder.setUpdateAvailableMenuVisibility).toHaveBeenCalledWith( - false, - ); - expect( - menuBuilder.setUpdateReadyForInstallMenuVisibility, - ).toHaveBeenCalledWith(false); + expect(menuBuilder.setCheckForUpdatesMenuEnabled).toHaveBeenCalledWith(true); + expect(menuBuilder.setNoUpdateAvailableMenuVisibility).toHaveBeenCalledWith(true); + expect(menuBuilder.setUpdateAvailableMenuVisibility).toHaveBeenCalledWith(false); + expect(menuBuilder.setUpdateReadyForInstallMenuVisibility).toHaveBeenCalledWith(false); }); it('auto-hides "No updates available" after configured timeout', async () => { @@ -211,15 +191,11 @@ describe('main/updater.ts', () => { emit('update-not-available'); // Immediately shows the message - expect( - menuBuilder.setNoUpdateAvailableMenuVisibility, - ).toHaveBeenCalledWith(true); + expect(menuBuilder.setNoUpdateAvailableMenuVisibility).toHaveBeenCalledWith(true); // Then hides it after the configured timeout vi.advanceTimersByTime(APPLICATION.UPDATE_NOT_AVAILABLE_DISPLAY_MS); - expect( - menuBuilder.setNoUpdateAvailableMenuVisibility, - ).toHaveBeenLastCalledWith(false); + expect(menuBuilder.setNoUpdateAvailableMenuVisibility).toHaveBeenLastCalledWith(false); } finally { vi.useRealTimers(); } @@ -233,24 +209,17 @@ describe('main/updater.ts', () => { emit('update-not-available'); // Message shown - expect( - menuBuilder.setNoUpdateAvailableMenuVisibility, - ).toHaveBeenCalledWith(true); + expect(menuBuilder.setNoUpdateAvailableMenuVisibility).toHaveBeenCalledWith(true); // New check should hide immediately and clear pending timeout emit('checking-for-update'); - expect( - menuBuilder.setNoUpdateAvailableMenuVisibility, - ).toHaveBeenLastCalledWith(false); + expect(menuBuilder.setNoUpdateAvailableMenuVisibility).toHaveBeenLastCalledWith(false); - const callsBefore = - menuBuilder.setNoUpdateAvailableMenuVisibility.mock.calls.length; + const callsBefore = menuBuilder.setNoUpdateAvailableMenuVisibility.mock.calls.length; vi.advanceTimersByTime(APPLICATION.UPDATE_NOT_AVAILABLE_DISPLAY_MS * 2); // No additional hide call due to cleared timeout - expect( - menuBuilder.setNoUpdateAvailableMenuVisibility.mock.calls.length, - ).toBe(callsBefore); + expect(menuBuilder.setNoUpdateAvailableMenuVisibility.mock.calls.length).toBe(callsBefore); } finally { vi.useRealTimers(); } @@ -262,9 +231,7 @@ describe('main/updater.ts', () => { emit('update-cancelled'); expect(menubar.tray.setToolTip).toHaveBeenCalledWith(APPLICATION.NAME); - expect(menuBuilder.setCheckForUpdatesMenuEnabled).toHaveBeenCalledWith( - true, - ); + expect(menuBuilder.setCheckForUpdatesMenuEnabled).toHaveBeenCalledWith(true); }); it('handles error (reset + logError)', async () => { @@ -273,28 +240,23 @@ describe('main/updater.ts', () => { const err = new Error('failure'); emit('error', err); - expect(logError).toHaveBeenCalledWith( - 'auto updater', - 'Error checking for update', - err, - ); + expect(logError).toHaveBeenCalledWith('auto updater', 'Error checking for update', err); expect(menubar.tray.setToolTip).toHaveBeenCalledWith(APPLICATION.NAME); }); it('performs initial check and schedules periodic checks', async () => { const originalSetInterval = globalThis.setInterval; - const setIntervalSpy = vi - .spyOn(globalThis, 'setInterval') - .mockImplementation(((fn: () => void) => { - fn(); - return 0 as unknown as NodeJS.Timeout; - }) as unknown as typeof setInterval); + const setIntervalSpy = vi.spyOn(globalThis, 'setInterval').mockImplementation((( + fn: () => void, + ) => { + fn(); + return 0 as unknown as NodeJS.Timeout; + }) as unknown as typeof setInterval); try { await updater.start(); // At minimum the initial check should have occurred - const callCount = vi.mocked(autoUpdater.checkForUpdatesAndNotify).mock - .calls.length; + const callCount = vi.mocked(autoUpdater.checkForUpdatesAndNotify).mock.calls.length; expect(callCount).toBeGreaterThanOrEqual(1); // If the periodic interval was scheduled during this test run, assert its arguments diff --git a/src/main/updater.ts b/src/main/updater.ts index 080a71b81..5a25a91a5 100644 --- a/src/main/updater.ts +++ b/src/main/updater.ts @@ -48,10 +48,7 @@ export default class AppUpdater { } if (!this.menubar.app.isPackaged) { - logInfo( - 'app updater', - 'Skipping updater since app is in development mode', - ); + logInfo('app updater', 'Skipping updater since app is in development mode'); return; } @@ -84,9 +81,7 @@ export default class AppUpdater { }); autoUpdater.on('download-progress', (progressObj) => { - this.setTooltipWithStatus( - `Downloading update: ${progressObj.percent.toFixed(2)}%`, - ); + this.setTooltipWithStatus(`Downloading update: ${progressObj.percent.toFixed(2)}%`); }); autoUpdater.on('update-downloaded', (event) => { @@ -151,10 +146,7 @@ export default class AppUpdater { // This avoids an immediate duplicate check on startup. setTimeout(async () => { await runScheduledCheck(); - this.periodicInterval = setInterval( - runScheduledCheck, - APPLICATION.UPDATE_CHECK_INTERVAL_MS, - ); + this.periodicInterval = setInterval(runScheduledCheck, APPLICATION.UPDATE_CHECK_INTERVAL_MS); }, APPLICATION.UPDATE_CHECK_INTERVAL_MS); } @@ -209,8 +201,7 @@ export default class AppUpdater { buttons: ['Restart', 'Later'], title: 'Application Update', message: `${APPLICATION.NAME} ${releaseName} has been downloaded`, - detail: - 'Restart to apply the update. You can also restart later from the tray menu.', + detail: 'Restart to apply the update. You can also restart later from the tray menu.', }; dialog.showMessageBox(dialogOpts).then((returnValue) => { diff --git a/src/main/utils.test.ts b/src/main/utils.test.ts index 68129782a..32fd77978 100644 --- a/src/main/utils.test.ts +++ b/src/main/utils.test.ts @@ -51,8 +51,7 @@ import { openLogsDirectory, takeScreenshot } from './utils'; function createMb() { return { window: { - capturePage: () => - Promise.resolve({ toPNG: () => Buffer.from('image-bytes') }), + capturePage: () => Promise.resolve({ toPNG: () => Buffer.from('image-bytes') }), }, app: { quit: vi.fn() }, }; diff --git a/src/preload/index.test.ts b/src/preload/index.test.ts index 2f5756ace..e8f8dfd68 100644 --- a/src/preload/index.test.ts +++ b/src/preload/index.test.ts @@ -38,8 +38,7 @@ vi.mock('../shared/logger', () => ({ vi.mock('electron', () => ({ contextBridge: { - exposeInMainWorld: (key: string, value: unknown) => - exposeInMainWorldMock(key, value), + exposeInMainWorld: (key: string, value: unknown) => exposeInMainWorldMock(key, value), }, webFrame: { getZoomLevel: () => getZoomLevelMock(), @@ -63,8 +62,7 @@ class MockNotification { } // Attach to global before importing preload -(global as unknown as { Notification: unknown }).Notification = - MockNotification; +(global as unknown as { Notification: unknown }).Notification = MockNotification; interface TestApi { tray: { @@ -107,16 +105,12 @@ describe('preload/index', () => { api.tray.updateColor(5, 'online', 'default', 'active'); - expect(sendMainEventMock).toHaveBeenNthCalledWith( - 1, - EVENTS.UPDATE_ICON_COLOR, - { - notificationsCount: 5, - appState: 'online', - idleIconVariant: 'default', - unreadIconVariant: 'active', - }, - ); + expect(sendMainEventMock).toHaveBeenNthCalledWith(1, EVENTS.UPDATE_ICON_COLOR, { + notificationsCount: 5, + appState: 'online', + idleIconVariant: 'default', + unreadIconVariant: 'active', + }); }); it('openExternalLink sends event with payload', async () => { @@ -155,10 +149,7 @@ describe('preload/index', () => { const callback = vi.fn(); api.onSystemThemeUpdate(callback); - expect(onRendererEventMock).toHaveBeenCalledWith( - EVENTS.UPDATE_THEME, - expect.any(Function), - ); + expect(onRendererEventMock).toHaveBeenCalledWith(EVENTS.UPDATE_THEME, expect.any(Function)); // Simulate event const listener = onRendererEventMock.mock.calls[0][1]; @@ -171,10 +162,7 @@ describe('preload/index', () => { it('raiseNativeNotification without url calls app.show', async () => { const api = getExposedApi(); - const notification = api.raiseNativeNotification( - 'Title', - 'Body', - ) as MockNotification; + const notification = api.raiseNativeNotification('Title', 'Body') as MockNotification; notification.triggerClick(); diff --git a/src/preload/index.ts b/src/preload/index.ts index 4e6fb0f24..ad8be181b 100644 --- a/src/preload/index.ts +++ b/src/preload/index.ts @@ -37,8 +37,7 @@ export const api = { * @param value - The plaintext string to encrypt. * @returns A promise resolving to the encrypted string. */ - encryptValue: (value: string) => - invokeMainEvent(EVENTS.SAFE_STORAGE_ENCRYPT, value), + encryptValue: (value: string) => invokeMainEvent(EVENTS.SAFE_STORAGE_ENCRYPT, value), /** * Decrypt an encrypted string using Electron's safe storage. @@ -46,8 +45,7 @@ export const api = { * @param value - The encrypted string to decrypt. * @returns A promise resolving to the plaintext string. */ - decryptValue: (value: string) => - invokeMainEvent(EVENTS.SAFE_STORAGE_DECRYPT, value), + decryptValue: (value: string) => invokeMainEvent(EVENTS.SAFE_STORAGE_DECRYPT, value), /** * Enable or disable launching the application at system login. @@ -221,10 +219,8 @@ export const api = { /** Aptabase analytics helpers. */ aptabase: { - trackEvent: ( - eventName: string, - props?: Record, - ) => sendMainEvent(EVENTS.APTABASE_TRACK_EVENT, { eventName, props }), + trackEvent: (eventName: string, props?: Record) => + sendMainEvent(EVENTS.APTABASE_TRACK_EVENT, { eventName, props }), }, }; @@ -235,9 +231,6 @@ export const api = { try { contextBridge.exposeInMainWorld('atlassify', api); } catch (err) { - // biome-ignore lint/suspicious/noConsole: preload environment is strictly sandboxed - console.error( - '[preload] Failed to expose Atlassify Bridge API to renderer', - err, - ); + // oxlint-disable-next-line no-console -- preload environment is strictly sandboxed + console.error('[preload] Failed to expose Atlassify Bridge API to renderer', err); } diff --git a/src/preload/utils.test.ts b/src/preload/utils.test.ts index 5a24810b4..943b31630 100644 --- a/src/preload/utils.test.ts +++ b/src/preload/utils.test.ts @@ -32,10 +32,7 @@ describe('preload/utils', () => { it('sendMainEvent forwards to ipcRenderer.send', () => { sendMainEvent(EVENTS.WINDOW_SHOW); - expect(ipcRenderer.send).toHaveBeenCalledWith( - EVENTS.WINDOW_SHOW, - undefined, - ); + expect(ipcRenderer.send).toHaveBeenCalledWith(EVENTS.WINDOW_SHOW, undefined); }); it('invokeMainEvent forwards and resolves', async () => { @@ -49,10 +46,7 @@ describe('preload/utils', () => { const handlerMock = vi.fn(); onRendererEvent( EVENTS.UPDATE_ICON_TITLE, - handlerMock as unknown as ( - e: Electron.IpcRendererEvent, - args: string, - ) => void, + handlerMock as unknown as (e: Electron.IpcRendererEvent, args: string) => void, ); ( ipcRenderer as unknown as { @@ -60,10 +54,7 @@ describe('preload/utils', () => { } ).__emit(EVENTS.UPDATE_ICON_TITLE, 'payload'); - expect(ipcRenderer.on).toHaveBeenCalledWith( - EVENTS.UPDATE_ICON_TITLE, - handlerMock, - ); + expect(ipcRenderer.on).toHaveBeenCalledWith(EVENTS.UPDATE_ICON_TITLE, handlerMock); expect(handlerMock).toHaveBeenCalledWith({}, 'payload'); }); }); diff --git a/src/preload/utils.ts b/src/preload/utils.ts index 43b90383e..026c9b702 100644 --- a/src/preload/utils.ts +++ b/src/preload/utils.ts @@ -19,14 +19,11 @@ export function sendMainEvent(event: EventType, data?: EventData): void { * @param data - Optional string payload to include with the event. * @returns A promise that resolves to the string response from the main process. */ -export async function invokeMainEvent( - event: EventType, - data?: EventData, -): Promise { +export async function invokeMainEvent(event: EventType, data?: EventData): Promise { try { return await ipcRenderer.invoke(event, data); } catch (err) { - // biome-ignore lint/suspicious/noConsole: preload environment is strictly sandboxed + // oxlint-disable-next-line no-console -- preload environment is strictly sandboxed console.error(`[IPC] invoke failed: ${event}`, err); throw err; } diff --git a/src/renderer/App.css b/src/renderer/App.css index f97c61e32..0e67d2e84 100644 --- a/src/renderer/App.css +++ b/src/renderer/App.css @@ -1,5 +1,5 @@ /** Tailwind CSS */ -@import "tailwindcss"; +@import 'tailwindcss'; /** Tailwind CSS Configuration */ @config "../../tailwind.config.mts"; diff --git a/src/renderer/App.tsx b/src/renderer/App.tsx index a596bb006..9efd3ccfd 100644 --- a/src/renderer/App.tsx +++ b/src/renderer/App.tsx @@ -1,16 +1,9 @@ import { type FC, useEffect } from 'react'; -import { - Navigate, - Route, - HashRouter as Router, - Routes, - useLocation, -} from 'react-router-dom'; +import { Navigate, Route, HashRouter as Router, Routes, useLocation } from 'react-router-dom'; import { QueryClientProvider } from '@tanstack/react-query'; import './App.css'; - import { AppProvider } from './context/AppContext'; import { AccountsRoute } from './routes/Accounts'; import { FiltersRoute } from './routes/Filters'; @@ -36,11 +29,7 @@ function RequireAuth({ children }) { const isLoggedIn = useAccountsStore((s) => s.isLoggedIn()); - return isLoggedIn ? ( - children - ) : ( - - ); + return isLoggedIn ? children : ; } export const App: FC = () => { diff --git a/src/renderer/__helpers__/test-utils.tsx b/src/renderer/__helpers__/test-utils.tsx index fccd324d0..c17a0ba61 100644 --- a/src/renderer/__helpers__/test-utils.tsx +++ b/src/renderer/__helpers__/test-utils.tsx @@ -1,8 +1,4 @@ -import { - type RenderHookOptions, - render, - renderHook, -} from '@testing-library/react'; +import { type RenderHookOptions, render, renderHook } from '@testing-library/react'; import { type ReactElement, type ReactNode, useMemo } from 'react'; import { MemoryRouter } from 'react-router-dom'; @@ -11,11 +7,7 @@ import axios from 'axios'; import { AppContext, type AppContextState } from '../context/AppContext'; import { useAccountsStore, useFiltersStore, useSettingsStore } from '../stores'; -import type { - AccountsStore, - FiltersStore, - SettingsStore, -} from '../stores/types'; +import type { AccountsStore, FiltersStore, SettingsStore } from '../stores/types'; export { navigateMock } from './vitest.setup'; @@ -64,9 +56,7 @@ function AppContextProvider({ return ( - - {children} - + {children} ); } @@ -113,13 +103,7 @@ export function renderHookWithProviders( */ export function renderWithProviders( ui: ReactElement, - { - initialEntries, - accounts, - settings, - filters, - ...context - }: RenderOptions = {}, + { initialEntries, accounts, settings, filters, ...context }: RenderOptions = {}, ) { if (accounts) { useAccountsStore.setState(accounts); diff --git a/src/renderer/__helpers__/vitest.setup.ts b/src/renderer/__helpers__/vitest.setup.ts index 60a877c03..883619ec0 100644 --- a/src/renderer/__helpers__/vitest.setup.ts +++ b/src/renderer/__helpers__/vitest.setup.ts @@ -1,5 +1,4 @@ import '@testing-library/jest-dom/vitest'; - import { mockAtlassianCloudAccount } from '../__mocks__/account-mocks'; import { useAccountsStore, useFiltersStore, useSettingsStore } from '../stores'; @@ -21,9 +20,7 @@ vi.mock('../utils/core/random', () => ({ vi.mock('axios', async (importOriginal) => { const actual = await importOriginal(); const realAxios = actual.default; - const wrapped = vi.fn((...args: Parameters) => - realAxios(...args), - ); + const wrapped = vi.fn((...args: Parameters) => realAxios(...args)); Object.assign(wrapped, realAxios); diff --git a/src/renderer/__mocks__/account-mocks.ts b/src/renderer/__mocks__/account-mocks.ts index 9c998b436..e7cef73d1 100644 --- a/src/renderer/__mocks__/account-mocks.ts +++ b/src/renderer/__mocks__/account-mocks.ts @@ -23,9 +23,7 @@ export const mockAtlassianCloudAccountTwo: Account = { avatar: 'https://avatar.atlassify.io' as Link, }; -export function createMockAccountWithError( - error: AtlassifyError, -): AccountNotifications { +export function createMockAccountWithError(error: AtlassifyError): AccountNotifications { return { account: mockAtlassianCloudAccount, notifications: [], diff --git a/src/renderer/__mocks__/notifications-mocks.ts b/src/renderer/__mocks__/notifications-mocks.ts index 3e0cc69ee..e5d2e38bf 100644 --- a/src/renderer/__mocks__/notifications-mocks.ts +++ b/src/renderer/__mocks__/notifications-mocks.ts @@ -1,9 +1,4 @@ -import type { - AccountNotifications, - AtlassifyNotification, - Link, - ProductType, -} from '../types'; +import type { AccountNotifications, AtlassifyNotification, Link, ProductType } from '../types'; import { PRODUCTS } from '../utils/products'; import { mockAtlassianCloudAccount } from './account-mocks'; @@ -79,8 +74,7 @@ export const mockAtlassifyNotifications: AtlassifyNotification[] = [ }, ]; -export const mockSingleAtlassifyNotification: AtlassifyNotification = - mockAtlassifyNotifications[0]; +export const mockSingleAtlassifyNotification: AtlassifyNotification = mockAtlassifyNotifications[0]; export const mockAccountNotifications: AccountNotifications[] = [ { diff --git a/src/renderer/components/AllRead.tsx b/src/renderer/components/AllRead.tsx index ea440049a..84ab53113 100644 --- a/src/renderer/components/AllRead.tsx +++ b/src/renderer/components/AllRead.tsx @@ -16,9 +16,7 @@ export const AllRead: FC = () => { const emoji = useMemo(() => randomElement(Constants.EMOJIS.ALL_READ), []); - const heading = hasFilters - ? t('allRead.headingFiltered') - : t('allRead.heading'); + const heading = hasFilters ? t('allRead.headingFiltered') : t('allRead.heading'); return ; }; diff --git a/src/renderer/components/GlobalShortcuts.test.tsx b/src/renderer/components/GlobalShortcuts.test.tsx index 63820ca76..ce3d0d693 100644 --- a/src/renderer/components/GlobalShortcuts.test.tsx +++ b/src/renderer/components/GlobalShortcuts.test.tsx @@ -71,9 +71,7 @@ describe('components/GlobalShortcuts.tsx', () => { await userEvent.keyboard('u'); - expect(toggleSettingsSpy).toHaveBeenCalledWith( - 'fetchOnlyUnreadNotifications', - ); + expect(toggleSettingsSpy).toHaveBeenCalledWith('fetchOnlyUnreadNotifications'); }); it('does not toggle read/unread when logged out', async () => { @@ -105,9 +103,7 @@ describe('components/GlobalShortcuts.tsx', () => { await userEvent.keyboard('p'); - expect(toggleSettingsSpy).toHaveBeenCalledWith( - 'groupNotificationsByProduct', - ); + expect(toggleSettingsSpy).toHaveBeenCalledWith('groupNotificationsByProduct'); }); it('does not toggle group by product when logged out', async () => { @@ -129,9 +125,7 @@ describe('components/GlobalShortcuts.tsx', () => { await userEvent.keyboard('t'); - expect(toggleSettingsSpy).toHaveBeenCalledWith( - 'groupNotificationsByTitle', - ); + expect(toggleSettingsSpy).toHaveBeenCalledWith('groupNotificationsByTitle'); }); it('does not toggle group by title when logged out', async () => { @@ -268,9 +262,7 @@ describe('components/GlobalShortcuts.tsx', () => { , ); - const input = document.getElementById( - 'test-input', - ) as HTMLTextAreaElement; + const input = document.getElementById('test-input') as HTMLTextAreaElement; input.focus(); await userEvent.type(input, 'h'); @@ -285,9 +277,7 @@ describe('components/GlobalShortcuts.tsx', () => { , ); - const textarea = document.getElementById( - 'test-textarea', - ) as HTMLTextAreaElement; + const textarea = document.getElementById('test-textarea') as HTMLTextAreaElement; textarea.focus(); await userEvent.type(textarea, 'h'); diff --git a/src/renderer/components/GlobalShortcuts.tsx b/src/renderer/components/GlobalShortcuts.tsx index 5defa7059..71813aebf 100644 --- a/src/renderer/components/GlobalShortcuts.tsx +++ b/src/renderer/components/GlobalShortcuts.tsx @@ -3,10 +3,7 @@ import { type FC, useEffect } from 'react'; import { useAppContext } from '../hooks/useAppContext'; import { useGlobalShortcuts } from '../hooks/useGlobalShortcuts'; -import { - getNormalizedKey, - shouldIgnoreKeyboardEvent, -} from '../utils/ui/keyboard'; +import { getNormalizedKey, shouldIgnoreKeyboardEvent } from '../utils/ui/keyboard'; /** * Component that registers global keyboard shortcuts for the renderer app. @@ -19,10 +16,7 @@ export const GlobalShortcuts: FC = () => { useEffect(() => { const keyToName = new Map( - Object.entries(shortcuts).map(([name, cfg]) => [ - cfg.key, - name as keyof typeof shortcuts, - ]), + Object.entries(shortcuts).map(([name, cfg]) => [cfg.key, name as keyof typeof shortcuts]), ); const handler = (event: KeyboardEvent) => { diff --git a/src/renderer/components/Sidebar.test.tsx b/src/renderer/components/Sidebar.test.tsx index 8dc85171c..2bc883033 100644 --- a/src/renderer/components/Sidebar.test.tsx +++ b/src/renderer/components/Sidebar.test.tsx @@ -26,9 +26,7 @@ const mockThemeObserverColorMode = (mode: 'light' | 'dark') => { describe('renderer/components/Sidebar.tsx', () => { const fetchNotificationsMock = vi.fn(); - const openExternalLinkSpy = vi - .spyOn(comms, 'openExternalLink') - .mockImplementation(vi.fn()); + const openExternalLinkSpy = vi.spyOn(comms, 'openExternalLink').mockImplementation(vi.fn()); describe('logged in', () => { it('should render itself & its children - light mode', () => { @@ -92,9 +90,7 @@ describe('renderer/components/Sidebar.tsx', () => { await userEvent.click(screen.getByTestId('sidebar-notifications')); - expect(openExternalLinkSpy).toHaveBeenCalledWith( - 'https://team.atlassian.com/notifications', - ); + expect(openExternalLinkSpy).toHaveBeenCalledWith('https://team.atlassian.com/notifications'); }); it('renders correct icon when there are no notifications', () => { @@ -142,17 +138,11 @@ describe('renderer/components/Sidebar.tsx', () => { it('should toggle show only unread notifications', async () => { renderWithProviders(); - expect( - screen.getByTestId('sidebar-toggle-unread-only--input'), - ).toBeChecked(); + expect(screen.getByTestId('sidebar-toggle-unread-only--input')).toBeChecked(); - await userEvent.click( - screen.getByTestId('sidebar-toggle-unread-only--toggle-cross-icon'), - ); + await userEvent.click(screen.getByTestId('sidebar-toggle-unread-only--toggle-cross-icon')); - expect( - screen.getByTestId('sidebar-toggle-unread-only--input'), - ).not.toBeChecked(); + expect(screen.getByTestId('sidebar-toggle-unread-only--input')).not.toBeChecked(); }); }); @@ -214,9 +204,7 @@ describe('renderer/components/Sidebar.tsx', () => { await userEvent.click(screen.getByTestId('sidebar-filter-notifications')); - expect( - screen.getByTestId('sidebar-filter-notifications'), - ).toMatchSnapshot(); + expect(screen.getByTestId('sidebar-filter-notifications')).toMatchSnapshot(); expect(navigateMock).toHaveBeenCalledTimes(1); expect(navigateMock).toHaveBeenCalledWith('/filters'); }); @@ -228,9 +216,7 @@ describe('renderer/components/Sidebar.tsx', () => { await userEvent.click(screen.getByTestId('sidebar-filter-notifications')); - expect( - screen.getByTestId('sidebar-filter-notifications'), - ).toMatchSnapshot(); + expect(screen.getByTestId('sidebar-filter-notifications')).toMatchSnapshot(); expect(navigateMock).toHaveBeenCalledTimes(1); expect(navigateMock).toHaveBeenCalledWith('/', { replace: true }); }); @@ -240,9 +226,7 @@ describe('renderer/components/Sidebar.tsx', () => { filters: { products: ['bitbucket'] }, }); - expect( - screen.getByTestId('sidebar-filter-notifications'), - ).toMatchSnapshot(); + expect(screen.getByTestId('sidebar-filter-notifications')).toMatchSnapshot(); }); }); diff --git a/src/renderer/components/Sidebar.tsx b/src/renderer/components/Sidebar.tsx index 159ab6aeb..1fb6d4ea7 100644 --- a/src/renderer/components/Sidebar.tsx +++ b/src/renderer/components/Sidebar.tsx @@ -43,15 +43,9 @@ const SidebarComponent: FC = () => { const isLoggedIn = useAccountsStore((s) => s.isLoggedIn()); // Setting store values - const fetchOnlyUnreadNotifications = useSettingsStore( - (s) => s.fetchOnlyUnreadNotifications, - ); - const groupNotificationsByProduct = useSettingsStore( - (s) => s.groupNotificationsByProduct, - ); - const groupNotificationsByTitle = useSettingsStore( - (s) => s.groupNotificationsByTitle, - ); + const fetchOnlyUnreadNotifications = useSettingsStore((s) => s.fetchOnlyUnreadNotifications); + const groupNotificationsByProduct = useSettingsStore((s) => s.groupNotificationsByProduct); + const groupNotificationsByTitle = useSettingsStore((s) => s.groupNotificationsByTitle); // Filter store values const hasFilters = useFiltersStore((s) => s.hasActiveFilters()); @@ -66,11 +60,7 @@ const SidebarComponent: FC = () => { - + } @@ -95,10 +85,7 @@ const SidebarComponent: FC = () => { ( - + )} label={t('sidebar.notifications.label')} onClick={() => shortcuts.myNotifications.action()} @@ -131,14 +118,9 @@ const SidebarComponent: FC = () => { shortcut={[shortcuts.groupByProduct.key]} > ( - + )} label={t('sidebar.toggles.groupByProduct.label')} onClick={() => shortcuts.groupByProduct.action()} @@ -154,14 +136,9 @@ const SidebarComponent: FC = () => { shortcut={[shortcuts.groupByTitle.key]} > ( - + )} label={t('sidebar.toggles.groupByTitle.label')} onClick={() => shortcuts.groupByTitle.action()} @@ -179,10 +156,7 @@ const SidebarComponent: FC = () => { ( - + )} label={t('sidebar.filters.label')} onClick={() => shortcuts.filters.action()} @@ -215,10 +189,7 @@ const SidebarComponent: FC = () => { size="medium" /> ) : ( - + ) } isDisabled={isFetching} @@ -237,10 +208,7 @@ const SidebarComponent: FC = () => { ( - + )} label={t('sidebar.settings.label')} onClick={() => shortcuts.settings.action()} @@ -260,10 +228,7 @@ const SidebarComponent: FC = () => { ( - + )} label={t('sidebar.quit.label', { appName: APPLICATION.NAME, diff --git a/src/renderer/components/filters/FilterSection.test.tsx b/src/renderer/components/filters/FilterSection.test.tsx index 01c0a25d7..e0f00c38b 100644 --- a/src/renderer/components/filters/FilterSection.test.tsx +++ b/src/renderer/components/filters/FilterSection.test.tsx @@ -47,11 +47,7 @@ describe('renderer/components/filters/FilterSection.tsx', () => { await userEvent.click(screen.getByLabelText('Mentions')); - expect(updateFilterSpy).toHaveBeenCalledWith( - mockFilterSetting, - 'mention', - true, - ); + expect(updateFilterSpy).toHaveBeenCalledWith(mockFilterSetting, 'mention', true); }); it('should be able to toggle filter value - some filters already set', async () => { @@ -69,10 +65,6 @@ describe('renderer/components/filters/FilterSection.tsx', () => { await userEvent.click(screen.getByLabelText('Comments')); - expect(updateFilterSpy).toHaveBeenCalledWith( - mockFilterSetting, - 'comment', - true, - ); + expect(updateFilterSpy).toHaveBeenCalledWith(mockFilterSetting, 'comment', true); }); }); diff --git a/src/renderer/components/filters/FilterSection.tsx b/src/renderer/components/filters/FilterSection.tsx index 8fc887023..32afac450 100644 --- a/src/renderer/components/filters/FilterSection.tsx +++ b/src/renderer/components/filters/FilterSection.tsx @@ -33,9 +33,7 @@ const FilterSectionComponent = ({ // Memoize filter counts to avoid recalculating on every render const filterCounts = useMemo(() => { const counts = new Map(); - for (const type of Object.keys( - filter.FILTER_TYPES, - ) as FiltersState[K][number][]) { + for (const type of Object.keys(filter.FILTER_TYPES) as FiltersState[K][number][]) { counts.set(type, filter.getFilterCount(notifications, type)); } return counts; @@ -45,67 +43,56 @@ const FilterSectionComponent = ({ {title} - {(Object.keys(filter.FILTER_TYPES) as FiltersState[K][number][]).map( - (type) => { - const typeDetails = filter.getTypeDetails(type); - const typeLabel = formatProperCase(typeDetails.name); - const isChecked = filter.isFilterSet(type); - const count = filterCounts.get(type) ?? 0; + {(Object.keys(filter.FILTER_TYPES) as FiltersState[K][number][]).map((type) => { + const typeDetails = filter.getTypeDetails(type); + const typeLabel = formatProperCase(typeDetails.name); + const isChecked = filter.isFilterSet(type); + const count = filterCounts.get(type) ?? 0; - return ( - - updateFilter(filterSetting, type, !isChecked)} + return ( + + updateFilter(filterSetting, type, !isChecked)} + /> + {typeDetails.icon && ( + - {typeDetails.icon && ( - - )} - {typeDetails.logo && ( - - )} - {typeDetails.heroicon && ( - - )} - - {count} - - - ); - }, - )} + )} + {typeDetails.logo && ( + + )} + {typeDetails.heroicon && ( + + )} + + {count} + + + ); + })} ); }; // Memoize the component to prevent unnecessary re-renders -export const FilterSection = memo( - FilterSectionComponent, -) as typeof FilterSectionComponent; +export const FilterSection = memo(FilterSectionComponent) as typeof FilterSectionComponent; diff --git a/src/renderer/components/layout/Contents.tsx b/src/renderer/components/layout/Contents.tsx index 081429b4a..a64c30147 100644 --- a/src/renderer/components/layout/Contents.tsx +++ b/src/renderer/components/layout/Contents.tsx @@ -9,9 +9,5 @@ interface ContentsProps { * It provides proper padding and handles scrolling. */ export const Contents: FC = (props: ContentsProps) => { - return ( -
- {props.children} -
- ); + return
{props.children}
; }; diff --git a/src/renderer/components/layout/EmojiSplash.test.tsx b/src/renderer/components/layout/EmojiSplash.test.tsx index fb7c1111e..a744ccc5f 100644 --- a/src/renderer/components/layout/EmojiSplash.test.tsx +++ b/src/renderer/components/layout/EmojiSplash.test.tsx @@ -10,9 +10,7 @@ describe('renderer/components/layout/EmojiSplash.tsx', () => { await act(async () => { tree = await act(async () => { - return renderWithProviders( - , - ); + return renderWithProviders(); }); }); @@ -25,11 +23,7 @@ describe('renderer/components/layout/EmojiSplash.tsx', () => { await act(async () => { tree = await act(async () => { return renderWithProviders( - , + , ); }); }); diff --git a/src/renderer/components/notifications/AccountNotifications.test.tsx b/src/renderer/components/notifications/AccountNotifications.test.tsx index 6ef6087c1..6fb62f102 100644 --- a/src/renderer/components/notifications/AccountNotifications.test.tsx +++ b/src/renderer/components/notifications/AccountNotifications.test.tsx @@ -7,10 +7,7 @@ import { mockAtlassifyNotifications } from '../../__mocks__/notifications-mocks' import * as links from '../../utils/system/links'; import * as theme from '../../utils/ui/theme'; -import { - AccountNotifications, - type AccountNotificationsProps, -} from './AccountNotifications'; +import { AccountNotifications, type AccountNotificationsProps } from './AccountNotifications'; vi.mock('./ProductNotifications', () => ({ ProductNotifications: () =>
ProductNotifications
, @@ -136,15 +133,9 @@ describe('renderer/components/notifications/AccountNotifications.tsx', () => { renderWithProviders(); - expect( - screen.queryByTestId('account-profile--itemInner'), - ).not.toBeInTheDocument(); - expect( - screen.queryByTestId('account-pull-requests'), - ).not.toBeInTheDocument(); - expect( - screen.queryByTestId('account-mark-as-read'), - ).not.toBeInTheDocument(); + expect(screen.queryByTestId('account-profile--itemInner')).not.toBeInTheDocument(); + expect(screen.queryByTestId('account-pull-requests')).not.toBeInTheDocument(); + expect(screen.queryByTestId('account-mark-as-read')).not.toBeInTheDocument(); expect(screen.queryByTestId('account-toggle')).not.toBeInTheDocument(); }); }); @@ -152,9 +143,7 @@ describe('renderer/components/notifications/AccountNotifications.tsx', () => { describe('account actions', () => { it('should open profile when clicked', async () => { - const openAccountProfileSpy = vi - .spyOn(links, 'openAccountProfile') - .mockImplementation(vi.fn()); + const openAccountProfileSpy = vi.spyOn(links, 'openAccountProfile').mockImplementation(vi.fn()); const props: AccountNotificationsProps = { account: mockAtlassianCloudAccount, @@ -171,15 +160,11 @@ describe('account actions', () => { await userEvent.click(screen.getByTestId('account-profile--itemInner')); expect(openAccountProfileSpy).toHaveBeenCalledTimes(1); - expect(openAccountProfileSpy).toHaveBeenCalledWith( - mockAtlassianCloudAccount, - ); + expect(openAccountProfileSpy).toHaveBeenCalledWith(mockAtlassianCloudAccount); }); it('should open my pull requests', async () => { - const openPullRequestsSpy = vi - .spyOn(links, 'openMyPullRequests') - .mockImplementation(vi.fn()); + const openPullRequestsSpy = vi.spyOn(links, 'openMyPullRequests').mockImplementation(vi.fn()); const props: AccountNotificationsProps = { account: mockAtlassianCloudAccount, @@ -257,9 +242,7 @@ describe('account actions', () => { renderWithProviders(); }); - expect( - screen.getAllByTestId('notification-details').length, - ).toBeGreaterThan(0); + expect(screen.getAllByTestId('notification-details').length).toBeGreaterThan(0); await userEvent.click(screen.getByTestId('account-toggle')); diff --git a/src/renderer/components/notifications/AccountNotifications.tsx b/src/renderer/components/notifications/AccountNotifications.tsx index 6aa2e7eb9..efc6115dc 100644 --- a/src/renderer/components/notifications/AccountNotifications.tsx +++ b/src/renderer/components/notifications/AccountNotifications.tsx @@ -22,20 +22,13 @@ import { Constants } from '../../constants'; import { useAppContext } from '../../hooks/useAppContext'; import { useSettingsStore } from '../../stores'; -import type { - Account, - AtlassifyError, - AtlassifyNotification, -} from '../../types'; +import type { Account, AtlassifyError, AtlassifyNotification } from '../../types'; import { groupNotificationsByProductEntries, sortNotificationsByOrder, } from '../../utils/notifications/group'; -import { - openAccountProfile, - openMyPullRequests, -} from '../../utils/system/links'; +import { openAccountProfile, openMyPullRequests } from '../../utils/system/links'; import { getChevronDetails } from '../../utils/ui/display'; import { isLightMode } from '../../utils/ui/theme'; import { AllRead } from '../AllRead'; @@ -54,18 +47,15 @@ export interface AccountNotificationsProps { export const AccountNotifications: FC = ( props: AccountNotificationsProps, ) => { - const { account, notifications, hasMoreNotifications, showAccountHeader } = - props; + const { account, notifications, hasMoreNotifications, showAccountHeader } = props; const { t } = useTranslation(); const { markNotificationsRead } = useAppContext(); - const [isAccountNotificationsVisible, setIsAccountNotificationsVisible] = - useState(true); + const [isAccountNotificationsVisible, setIsAccountNotificationsVisible] = useState(true); - const [showMarkAccountAsReadModal, setShowMarkAccountAsReadModal] = - useState(false); + const [showMarkAccountAsReadModal, setShowMarkAccountAsReadModal] = useState(false); const actionOpenMarkAccountAsReadModal = () => { setShowMarkAccountAsReadModal(true); @@ -147,10 +137,7 @@ export const AccountNotifications: FC = ( > - + = ( - + ( - - )} + icon={(iconProps) => } label={t('notifications.account.pull_requests')} onClick={(event: MouseEvent) => { event.stopPropagation(); @@ -197,10 +179,7 @@ export const AccountNotifications: FC = ( /> - + } @@ -217,9 +196,7 @@ export const AccountNotifications: FC = ( ( - - )} + icon={(iconProps) => } label={Chevron.label} shape="circle" spacing="compact" @@ -238,14 +215,12 @@ export const AccountNotifications: FC = ( {!hasAccountNotifications && !props.error && } {groupByProduct - ? groupedNotifications.map( - ([productType, productNotifications]) => ( - - ), - ) + ? groupedNotifications.map(([productType, productNotifications]) => ( + + )) : sortedNotifications.map((notification) => ( = ( {showMarkAccountAsReadModal && ( - + = ( /> - - {t('common.are_you_sure')} - + {t('common.are_you_sure')}

{t('notifications.account.mark_read_confirm.description1')}{' '} - - {t('notifications.account.mark_read_confirm.description2')} - {' '} + {t('notifications.account.mark_read_confirm.description2')}{' '} {t('notifications.account.mark_read_confirm.description3')}

diff --git a/src/renderer/components/notifications/NotificationActions.test.tsx b/src/renderer/components/notifications/NotificationActions.test.tsx index a7ab2850e..9d20f3af2 100644 --- a/src/renderer/components/notifications/NotificationActions.test.tsx +++ b/src/renderer/components/notifications/NotificationActions.test.tsx @@ -3,10 +3,7 @@ import userEvent from '@testing-library/user-event'; import { renderWithProviders } from '../../__helpers__/test-utils'; -import { - NotificationActions, - type NotificationActionsProps, -} from './NotificationActions'; +import { NotificationActions, type NotificationActionsProps } from './NotificationActions'; describe('renderer/components/notifications/NotificationActions.tsx', () => { it('renders mark-as-read button when unread and not animating', () => { @@ -45,12 +42,8 @@ describe('renderer/components/notifications/NotificationActions.tsx', () => { renderWithProviders(); - expect( - screen.queryByTestId('notification-mark-as-read'), - ).not.toBeInTheDocument(); - expect( - screen.queryByTestId('notification-mark-as-unread'), - ).not.toBeInTheDocument(); + expect(screen.queryByTestId('notification-mark-as-read')).not.toBeInTheDocument(); + expect(screen.queryByTestId('notification-mark-as-unread')).not.toBeInTheDocument(); }); it('calls onMarkAsRead when mark-as-read is clicked', async () => { diff --git a/src/renderer/components/notifications/NotificationActions.tsx b/src/renderer/components/notifications/NotificationActions.tsx index 8818bc5ff..ef583940b 100644 --- a/src/renderer/components/notifications/NotificationActions.tsx +++ b/src/renderer/components/notifications/NotificationActions.tsx @@ -26,18 +26,10 @@ export const NotificationActions: FC = ({ {!isAnimatingExit && (isUnread ? ( - + ( - - )} + icon={() => } label={t('notifications.interactions.mark_as_read')} onClick={onMarkAsRead} shape="circle" @@ -46,10 +38,7 @@ export const NotificationActions: FC = ({ /> ) : ( - + null} diff --git a/src/renderer/components/notifications/NotificationAvatar.test.tsx b/src/renderer/components/notifications/NotificationAvatar.test.tsx index b45b040b1..488970898 100644 --- a/src/renderer/components/notifications/NotificationAvatar.test.tsx +++ b/src/renderer/components/notifications/NotificationAvatar.test.tsx @@ -2,10 +2,7 @@ import { renderWithProviders } from '../../__helpers__/test-utils'; import { mockSingleAtlassifyNotification } from '../../__mocks__/notifications-mocks'; import { PRODUCTS } from '../../utils/products'; -import { - NotificationAvatar, - type NotificationAvatarProps, -} from './NotificationAvatar'; +import { NotificationAvatar, type NotificationAvatarProps } from './NotificationAvatar'; describe('renderer/components/notifications/NotificationAvatar.tsx', () => { it('renders actor avatar with circle appearance', () => { diff --git a/src/renderer/components/notifications/NotificationContent.test.tsx b/src/renderer/components/notifications/NotificationContent.test.tsx index 503a998e6..3ca66a7e7 100644 --- a/src/renderer/components/notifications/NotificationContent.test.tsx +++ b/src/renderer/components/notifications/NotificationContent.test.tsx @@ -4,15 +4,10 @@ import userEvent from '@testing-library/user-event'; import { renderWithProviders } from '../../__helpers__/test-utils'; import { mockSingleAtlassifyNotification } from '../../__mocks__/notifications-mocks'; -import { - NotificationContent, - type NotificationContentProps, -} from './NotificationContent'; +import { NotificationContent, type NotificationContentProps } from './NotificationContent'; describe('renderer/components/notifications/NotificationContent.tsx', () => { - vi.spyOn(globalThis.Date, 'now').mockImplementation(() => - new Date('2024').valueOf(), - ); + vi.spyOn(globalThis.Date, 'now').mockImplementation(() => new Date('2024').valueOf()); it('renders notification message, body and footer', () => { const props: NotificationContentProps = { diff --git a/src/renderer/components/notifications/NotificationContent.tsx b/src/renderer/components/notifications/NotificationContent.tsx index bbfec25cd..75e165415 100644 --- a/src/renderer/components/notifications/NotificationContent.tsx +++ b/src/renderer/components/notifications/NotificationContent.tsx @@ -24,24 +24,20 @@ export const NotificationContent: FC = ({ }: NotificationContentProps) => { const updatedAt = formatNotificationUpdatedAt(notification); - const avatarGroup: AvatarProps[] = - notification.notificationGroup.additionalActors.map((actor) => ({ + const avatarGroup: AvatarProps[] = notification.notificationGroup.additionalActors.map( + (actor) => ({ key: actor.displayName, name: actor.displayName, href: '#', src: actor.avatarURL, - })); + }), + ); const displayGroupSize = notification.notificationGroup.size - 1; const displayUpdateVerbiage = displayGroupSize > 1 ? 'updates' : 'update'; return ( - +
@@ -58,15 +54,11 @@ export const NotificationContent: FC = ({ as="div" hidden={!bodyText} id="notification-entity" - paddingInlineStart={ - notification.entity.iconUrl ? 'space.0' : 'space.025' - } + paddingInlineStart={notification.entity.iconUrl ? 'space.0' : 'space.025'} > {notification.entity.iconUrl ? ( = ({ - - + + = ({ {notification.notificationGroup.size > 1 && ( - {notification.notificationGroup.additionalActors.length > - 0 && ( + {notification.notificationGroup.additionalActors.length > 0 && ( // @ts-expect-error We're forcing the xsmall size for Avatar Groups )} +{displayGroupSize}{' '} - {notification.notificationGroup.additionalActors.length > - 0 + {notification.notificationGroup.additionalActors.length > 0 ? `${displayUpdateVerbiage} from ${notification.notificationGroup.additionalActors[0].displayName}` : `other ${displayUpdateVerbiage}`} - {notification.notificationGroup.additionalActors.length > - 1 && ' and others'} + {notification.notificationGroup.additionalActors.length > 1 && ' and others'} )} diff --git a/src/renderer/components/notifications/NotificationRow.test.tsx b/src/renderer/components/notifications/NotificationRow.test.tsx index 39a92afbc..91734e5f8 100644 --- a/src/renderer/components/notifications/NotificationRow.test.tsx +++ b/src/renderer/components/notifications/NotificationRow.test.tsx @@ -13,9 +13,7 @@ import { NotificationRow, type NotificationRowProps } from './NotificationRow'; describe('renderer/components/notifications/NotificationRow.tsx', () => { vi.spyOn(links, 'openNotification').mockImplementation(vi.fn()); vi.spyOn(comms, 'openExternalLink').mockImplementation(vi.fn()); - vi.spyOn(globalThis.Date, 'now').mockImplementation(() => - new Date('2024').valueOf(), - ); + vi.spyOn(globalThis.Date, 'now').mockImplementation(() => new Date('2024').valueOf()); describe('should render notifications', () => { it('standard notification', async () => { @@ -65,9 +63,7 @@ describe('renderer/components/notifications/NotificationRow.tsx', () => { // Trigger transitionEnd to complete the animation and execute mutation await act(async () => { - notificationElement?.dispatchEvent( - new Event('transitionend', { bubbles: true }), - ); + notificationElement?.dispatchEvent(new Event('transitionend', { bubbles: true })); }); expect(links.openNotification).toHaveBeenCalledTimes(1); @@ -114,9 +110,7 @@ describe('renderer/components/notifications/NotificationRow.tsx', () => { // Trigger transitionEnd to complete the animation and execute mutation await act(async () => { - notificationElement?.dispatchEvent( - new Event('transitionend', { bubbles: true }), - ); + notificationElement?.dispatchEvent(new Event('transitionend', { bubbles: true })); }); expect(markNotificationsReadMock).toHaveBeenCalledTimes(1); diff --git a/src/renderer/components/notifications/NotificationRow.tsx b/src/renderer/components/notifications/NotificationRow.tsx index bdac5ff93..aa3217cab 100644 --- a/src/renderer/components/notifications/NotificationRow.tsx +++ b/src/renderer/components/notifications/NotificationRow.tsx @@ -28,14 +28,9 @@ export const NotificationRow: FC = ({ }: NotificationRowProps) => { const markAsReadOnOpen = useSettingsStore((s) => s.markAsReadOnOpen); - const { - markNotificationsRead, - markNotificationsUnread, - focusedNotificationId, - } = useAppContext(); - - const [shouldAnimateNotificationExit, setShouldAnimateNotificationExit] = - useState(false); + const { markNotificationsRead, markNotificationsUnread, focusedNotificationId } = useAppContext(); + + const [shouldAnimateNotificationExit, setShouldAnimateNotificationExit] = useState(false); const [pendingMarkAsRead, setPendingMarkAsRead] = useState(false); const isFocused = focusedNotificationId === notification.id; @@ -75,10 +70,7 @@ export const NotificationRow: FC = ({ } }; - const isNotificationUnread = readStateFilter.filterNotification( - notification, - 'unread', - ); + const isNotificationUnread = readStateFilter.filterNotification(notification, 'unread'); const strategy = getProductStrategy(notification); @@ -94,8 +86,7 @@ export const NotificationRow: FC = ({ className={cn( 'border-b border-atlassify-notifications hover:bg-atlassify-notifications', isFocused && 'bg-atlassify-notifications', - (isProductAnimatingExit || shouldAnimateNotificationExit) && - 'notification-exit', + (isProductAnimatingExit || shouldAnimateNotificationExit) && 'notification-exit', )} data-notification-id={notification.id} data-notification-row="true" diff --git a/src/renderer/components/notifications/ProductNotifications.test.tsx b/src/renderer/components/notifications/ProductNotifications.test.tsx index febf91458..a465dc5f3 100644 --- a/src/renderer/components/notifications/ProductNotifications.test.tsx +++ b/src/renderer/components/notifications/ProductNotifications.test.tsx @@ -6,18 +6,13 @@ import { mockAtlassifyNotifications } from '../../__mocks__/notifications-mocks' import * as comms from '../../utils/system/comms'; import * as theme from '../../utils/ui/theme'; -import { - ProductNotifications, - type ProductNotificationsProps, -} from './ProductNotifications'; +import { ProductNotifications, type ProductNotificationsProps } from './ProductNotifications'; vi.mock('./NotificationRow', () => ({ NotificationRow: () =>
NotificationRow
, })); -const openExternalLinkSpy = vi - .spyOn(comms, 'openExternalLink') - .mockImplementation(vi.fn()); +const openExternalLinkSpy = vi.spyOn(comms, 'openExternalLink').mockImplementation(vi.fn()); describe('renderer/components/notifications/ProductNotifications.tsx', () => { it('should render itself & its children - light mode', () => { @@ -69,9 +64,7 @@ describe('renderer/components/notifications/ProductNotifications.tsx', () => { await act(async () => { renderWithProviders( - , + , ); }); diff --git a/src/renderer/components/notifications/ProductNotifications.tsx b/src/renderer/components/notifications/ProductNotifications.tsx index f6708d443..8a048d4fc 100644 --- a/src/renderer/components/notifications/ProductNotifications.tsx +++ b/src/renderer/components/notifications/ProductNotifications.tsx @@ -21,18 +21,14 @@ export interface ProductNotificationsProps { productNotifications: AtlassifyNotification[]; } -export const ProductNotifications: FC = ({ - productNotifications, -}) => { +export const ProductNotifications: FC = ({ productNotifications }) => { const { t } = useTranslation(); const { markNotificationsRead } = useAppContext(); - const [shouldAnimateProductExit, setShouldAnimateProductExit] = - useState(false); + const [shouldAnimateProductExit, setShouldAnimateProductExit] = useState(false); const [pendingMarkAsRead, setPendingMarkAsRead] = useState(false); - const [isProductNotificationsVisible, setIsProductNotificationsVisible] = - useState(true); + const [isProductNotificationsVisible, setIsProductNotificationsVisible] = useState(true); // We assume that productNotifications are all of the same product-type, as grouped within AccountNotifications const productNotification = productNotifications[0].product; @@ -65,11 +61,7 @@ export const ProductNotifications: FC = ({ setIsProductNotificationsVisible(!isProductNotificationsVisible); }; - const Chevron = getChevronDetails( - true, - isProductNotificationsVisible, - 'product', - ); + const Chevron = getChevronDetails(true, isProductNotificationsVisible, 'product'); const ChevronIcon = Chevron.icon; const boxStyles = xcss({ @@ -124,19 +116,14 @@ export const ProductNotifications: FC = ({ shouldUseNewLogoDesign size="xxsmall" /> - - {productNotification.display} - + {productNotification.display} {productNotifications.length}
- + } @@ -155,9 +142,7 @@ export const ProductNotifications: FC = ({ ( - - )} + icon={(iconProps) => } label={Chevron.label} shape="circle" spacing="compact" diff --git a/src/renderer/components/primitives/Footer.test.tsx b/src/renderer/components/primitives/Footer.test.tsx index 82533dad5..0a27623d1 100644 --- a/src/renderer/components/primitives/Footer.test.tsx +++ b/src/renderer/components/primitives/Footer.test.tsx @@ -4,9 +4,7 @@ import { Footer } from './Footer'; describe('renderer/components/primitives/Footer.tsx', () => { it('should render itself & its children - space-between', () => { - const tree = renderWithProviders( -
Test
, - ); + const tree = renderWithProviders(
Test
); expect(tree.container).toMatchSnapshot(); }); diff --git a/src/renderer/components/primitives/Footer.tsx b/src/renderer/components/primitives/Footer.tsx index 8209f3665..b654b9822 100644 --- a/src/renderer/components/primitives/Footer.tsx +++ b/src/renderer/components/primitives/Footer.tsx @@ -13,11 +13,7 @@ export const Footer: FC = (props: FooterProps) => { }); return ( - + {props.children} ); diff --git a/src/renderer/components/primitives/Header.test.tsx b/src/renderer/components/primitives/Header.test.tsx index 51fbec0ab..f908caee8 100644 --- a/src/renderer/components/primitives/Header.test.tsx +++ b/src/renderer/components/primitives/Header.test.tsx @@ -1,10 +1,7 @@ import { screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; -import { - navigateMock, - renderWithProviders, -} from '../../__helpers__/test-utils'; +import { navigateMock, renderWithProviders } from '../../__helpers__/test-utils'; import { Header } from './Header'; diff --git a/src/renderer/components/primitives/Header.tsx b/src/renderer/components/primitives/Header.tsx index 16366a4ac..d9a457e91 100644 --- a/src/renderer/components/primitives/Header.tsx +++ b/src/renderer/components/primitives/Header.tsx @@ -20,11 +20,7 @@ export const Header: FC = (props: HeaderProps) => { const { fetchNotifications } = useAppContext(); return ( - + { }); it('should update the zoom values when using the zoom buttons', async () => { - const zoomOutSpy = vi - .spyOn(zoom, 'decreaseZoom') - .mockImplementation(vi.fn()); - const zoomInSpy = vi - .spyOn(zoom, 'increaseZoom') - .mockImplementation(vi.fn()); - const zoomResetSpy = vi - .spyOn(zoom, 'resetZoomLevel') - .mockImplementation(vi.fn()); + const zoomOutSpy = vi.spyOn(zoom, 'decreaseZoom').mockImplementation(vi.fn()); + const zoomInSpy = vi.spyOn(zoom, 'increaseZoom').mockImplementation(vi.fn()); + const zoomResetSpy = vi.spyOn(zoom, 'resetZoomLevel').mockImplementation(vi.fn()); // Zoom Out await userEvent.click(screen.getByTestId('settings-zoom-out')); diff --git a/src/renderer/components/settings/AppearanceSettings.tsx b/src/renderer/components/settings/AppearanceSettings.tsx index d3d4a04d7..fcc2f39a2 100644 --- a/src/renderer/components/settings/AppearanceSettings.tsx +++ b/src/renderer/components/settings/AppearanceSettings.tsx @@ -74,9 +74,7 @@ export const AppearanceSettings: FC = () => { {t('settings.appearance.language')}: