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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,9 @@
# Ignore massive diffs each time you add/update yarn plugins
/web/.yarn/releases/** binary
/web/.yarn/plugins/** binary

# Generated files — hide from GitHub diffs/language stats and mark as not
# hand-edited. The TanStack Router Vite plugin owns routeTree.gen.ts;
# `@hey-api/openapi-ts` owns api-generated/.
/web/src/app/routeTree.gen.ts linguist-generated=true
/web/src/api-generated/** linguist-generated=true
7 changes: 7 additions & 0 deletions .mise/tasks/lint.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,17 @@ depends = ["install-dependencies:web"]
dir = "{{config_root}}/web"
run = "yarn compile"

["lint:web:query"]
description = "Lint TanStack Query usage in the Web application"
depends = ["install-dependencies:web"]
dir = "{{config_root}}/web"
run = "yarn lint:query"

["lint:web"]
description = "Lint the Web application"
run = [
"mise run lint:web:typecheck",
"mise run lint:web:query",
]

[lint]
Expand Down
16 changes: 14 additions & 2 deletions biome.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,14 @@
{
"$schema": "https://biomejs.dev/schemas/2.4.9/schema.json",
"$schema": "https://biomejs.dev/schemas/2.4.14/schema.json",
"files": {
"includes": ["web/**", "!web/build", "!web/src/api/generated"]
"includes": [
"web/**",
"!web/build",
"!web/.yarn",
"!web/.pnp.*",
"!web/src/api-generated",
"!web/src/app/routeTree.gen.ts"
]
},
"javascript": {
"formatter": {
Expand All @@ -19,6 +26,11 @@
"indentWidth": 2
}
},
"css": {
"parser": {
"tailwindDirectives": true
}
},
"linter": {
"rules": {
"a11y": {
Expand Down
10 changes: 10 additions & 0 deletions docker-compose.override.yml
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,16 @@ services:
- .env
environment:
- NODE_ENV=development
# Vite reads VITE_-prefixed env vars at dev-server start. The Dockerfile
# bakes a default of 0 from the build arg; override it here so the SPA
# actually fetches /oauth2/userinfo instead of falling back to the
# local-developer anonymous user.
- VITE_AUTH=$AUTH_ENABLED
# Telemetry: SPA ships events to `/api/monitoring/v2/track` (see
# appInsightsProxyPath default) and the API re-exports them to Azure
# Monitor using its own APPINSIGHTS_CONSTRING. The SPA needs no
# connection string of its own — see web/src/config/env.ts.
- VITE_TELEMETRY=${VITE_TELEMETRY:-appinsights}

db:
volumes:
Expand Down
32 changes: 32 additions & 0 deletions web/.yarn/sdks/eslint/bin/eslint.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
#!/usr/bin/env node

const {existsSync} = require(`fs`);
const {createRequire, register} = require(`module`);
const {resolve} = require(`path`);
const {pathToFileURL} = require(`url`);

const relPnpApiPath = "../../../../.pnp.cjs";

const absPnpApiPath = resolve(__dirname, relPnpApiPath);
const absUserWrapperPath = resolve(__dirname, `./sdk.user.cjs`);
const absRequire = createRequire(absPnpApiPath);

const absPnpLoaderPath = resolve(absPnpApiPath, `../.pnp.loader.mjs`);
const isPnpLoaderEnabled = existsSync(absPnpLoaderPath);

if (existsSync(absPnpApiPath)) {
if (!process.versions.pnp) {
// Setup the environment to be able to require eslint/bin/eslint.js
require(absPnpApiPath).setup();
if (isPnpLoaderEnabled && register) {
register(pathToFileURL(absPnpLoaderPath));
}
}
}

const wrapWithUserWrapper = existsSync(absUserWrapperPath)
? exports => absRequire(absUserWrapperPath)(exports)
: exports => exports;

// Defer to the real eslint/bin/eslint.js your application uses
module.exports = wrapWithUserWrapper(absRequire(`eslint/bin/eslint.js`));
31 changes: 31 additions & 0 deletions web/.yarn/sdks/eslint/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
{
"name": "eslint",
"version": "10.3.0-sdk",
"main": "./lib/api.js",
"type": "commonjs",
"bin": {
"eslint": "./bin/eslint.js"
},
"exports": {
".": {
"types": "./lib/types/index.d.ts",
"default": "./lib/api.js"
},
"./config": {
"types": "./lib/types/config-api.d.ts",
"default": "./lib/config-api.js"
},
"./package.json": "./package.json",
"./use-at-your-own-risk": {
"types": "./lib/types/use-at-your-own-risk.d.ts",
"default": "./lib/unsupported-api.js"
},
"./rules": {
"types": "./lib/types/rules.d.ts"
},
"./universal": {
"types": "./lib/types/universal.d.ts",
"default": "./lib/universal.js"
}
}
}
5 changes: 5 additions & 0 deletions web/.yarn/sdks/integrations.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# This file is automatically generated by @yarnpkg/sdks.
# Manual changes might be lost!

integrations:
- vscode
32 changes: 32 additions & 0 deletions web/.yarn/sdks/typescript/bin/tsc
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
#!/usr/bin/env node

const {existsSync} = require(`fs`);
const {createRequire, register} = require(`module`);
const {resolve} = require(`path`);
const {pathToFileURL} = require(`url`);

const relPnpApiPath = "../../../../.pnp.cjs";

const absPnpApiPath = resolve(__dirname, relPnpApiPath);
const absUserWrapperPath = resolve(__dirname, `./sdk.user.cjs`);
const absRequire = createRequire(absPnpApiPath);

const absPnpLoaderPath = resolve(absPnpApiPath, `../.pnp.loader.mjs`);
const isPnpLoaderEnabled = existsSync(absPnpLoaderPath);

if (existsSync(absPnpApiPath)) {
if (!process.versions.pnp) {
// Setup the environment to be able to require typescript/bin/tsc
require(absPnpApiPath).setup();
if (isPnpLoaderEnabled && register) {
register(pathToFileURL(absPnpLoaderPath));
}
}
}

const wrapWithUserWrapper = existsSync(absUserWrapperPath)
? exports => absRequire(absUserWrapperPath)(exports)
: exports => exports;

// Defer to the real typescript/bin/tsc your application uses
module.exports = wrapWithUserWrapper(absRequire(`typescript/bin/tsc`));
32 changes: 32 additions & 0 deletions web/.yarn/sdks/typescript/bin/tsserver
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
#!/usr/bin/env node

const {existsSync} = require(`fs`);
const {createRequire, register} = require(`module`);
const {resolve} = require(`path`);
const {pathToFileURL} = require(`url`);

const relPnpApiPath = "../../../../.pnp.cjs";

const absPnpApiPath = resolve(__dirname, relPnpApiPath);
const absUserWrapperPath = resolve(__dirname, `./sdk.user.cjs`);
const absRequire = createRequire(absPnpApiPath);

const absPnpLoaderPath = resolve(absPnpApiPath, `../.pnp.loader.mjs`);
const isPnpLoaderEnabled = existsSync(absPnpLoaderPath);

if (existsSync(absPnpApiPath)) {
if (!process.versions.pnp) {
// Setup the environment to be able to require typescript/bin/tsserver
require(absPnpApiPath).setup();
if (isPnpLoaderEnabled && register) {
register(pathToFileURL(absPnpLoaderPath));
}
}
}

const wrapWithUserWrapper = existsSync(absUserWrapperPath)
? exports => absRequire(absUserWrapperPath)(exports)
: exports => exports;

// Defer to the real typescript/bin/tsserver your application uses
module.exports = wrapWithUserWrapper(absRequire(`typescript/bin/tsserver`));
10 changes: 10 additions & 0 deletions web/.yarn/sdks/typescript/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"name": "typescript",
"version": "6.0.3-sdk",
"main": "./lib/typescript.js",
"type": "commonjs",
"bin": {
"tsc": "./bin/tsc",
"tsserver": "./bin/tsserver"
}
}
3 changes: 2 additions & 1 deletion web/.yarnrc.yml
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
nodeLinker: pnp
enableGlobalCache: true

nodeLinker: pnp
119 changes: 119 additions & 0 deletions web/eslint.config.mts
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
// Minimal ESLint flat config — runs ONLY @tanstack/eslint-plugin-query rules
// alongside Biome. Biome remains the formatter and primary linter; this file
// hosts the TanStack-specific rules Biome cannot express
// (queryKey-aware exhaustive-deps, prefer-query-options, mutation/infinite
// property order, etc.).
//
// Run with: `yarn lint:query` (or via mise: `mise run lint:web:query`).

import pluginQuery from '@tanstack/eslint-plugin-query'
import tseslint from 'typescript-eslint'

const recommended = pluginQuery.configs['flat/recommended']
const recommendedRules = {}
for (const c of Array.isArray(recommended) ? recommended : [recommended]) {
Object.assign(recommendedRules, c.rules ?? {})
}

export default [
{
ignores: ['src/api-generated/**', 'build/**', 'node_modules/**', '.yarn/**'],
},
{
files: ['src/**/*.{ts,tsx}'],
// We only enable the @tanstack/query rules — silence reports about
// pre-existing `eslint-disable` directives for rules we don't load.
linterOptions: {
reportUnusedDisableDirectives: 'off',
},
languageOptions: {
parser: tseslint.parser,
parserOptions: {
ecmaVersion: 'latest',
sourceType: 'module',
ecmaFeatures: { jsx: true },
},
},
plugins: {
'@tanstack/query': pluginQuery,
},
rules: {
...recommendedRules,
// Feature-slice boundary: outside code may only import a feature
// through its public barrel (`@/features/<name>`), never deep paths
// like `@/features/<name>/api/...`. Inside-feature code uses
// relative paths and is exempted by the override below.
'no-restricted-imports': [
'error',
{
patterns: [
{
group: ['@/features/*/*'],
message:
'Import from the feature barrel (`@/features/<name>`), not its internals. ' +
'If you need something not exported, add it to the barrel.',
},
{
group: ['@/shared/platform/*/*'],
message:
'Import from the platform module barrel (`@/shared/platform/<name>`), not its ' +
'internals. If you need something not exported, add it to the barrel.',
},
],
},
],
},
},
{
// Inside a feature, deep imports are fine — but prefer relative paths
// (the rule still nudges that direction by allowing only relatives here).
files: ['src/features/*/**/*.{ts,tsx}'],
rules: {
'no-restricted-imports': 'off',
},
},
{
// Layering: `shared/` is a downstream layer. It must not depend on
// `features/` or `app/` — that would create a cycle (features import
// shared; app composes both). Pure utilities only.
files: ['src/shared/**/*.{ts,tsx}'],
rules: {
'no-restricted-imports': [
'error',
{
patterns: [
{
group: ['@/features/*', '@/features/*/*', '@/app/*', '@/app/*/*'],
message:
'`shared/` must not import from `features/` or `app/`. ' +
'Move the symbol down to `shared/` or invert the dependency.',
},
],
},
],
},
},
{
// Layering: `config/` may import `features/*` *barrels* (e.g.
// `accessControl.ts` types its `Permissions` map against `Todo` from
// `@/features/todos`). It must not depend on `app/`.
files: ['src/config/**/*.{ts,tsx}'],
rules: {
'no-restricted-imports': [
'error',
{
patterns: [
{
group: ['@/app/*', '@/app/*/*'],
message: '`config/` must not import from `app/`.',
},
{
group: ['@/features/*/*'],
message: 'Import from the feature barrel (`@/features/<name>`), not its internals.',
},
],
},
],
},
},
]
10 changes: 10 additions & 0 deletions web/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,16 @@

<body>
<div id="root"></div>
<script>
// Apply saved color-scheme before React mounts to prevent a flash.
// Mirrors the logic in src/shared/platform/theme/useColorScheme.ts.
try {
const saved = localStorage.getItem('color-scheme')
if (saved === 'light' || saved === 'dark') {
document.documentElement.style.colorScheme = saved
}
} catch (_) {}
</script>
<script type="module" src="./src/index.tsx"></script>
</body>

Expand Down
4 changes: 3 additions & 1 deletion web/openapi-ts.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,14 @@ import type { UserConfig } from '@hey-api/openapi-ts'

export default {
input: '../api/.openapi.json',
output: './src/api/generated',
output: './src/api-generated',
plugins: [
{
name: '@hey-api/client-fetch',
},
'@hey-api/typescript',
'@hey-api/sdk',
'@tanstack/react-query',
'zod',
],
} satisfies UserConfig
Loading
Loading