Skip to content
Merged
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
13 changes: 8 additions & 5 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,10 @@ jobs:

steps:
- name: Check out repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2

- name: Set up Node.js
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
with:
node-version: 24
cache: npm
Expand All @@ -37,6 +37,9 @@ jobs:
- name: Run ESLint
run: npm run lint

- name: Run docstring lint
run: npm run lint:docstrings

- name: Run TypeScript checks
run: ./node_modules/.bin/tsc --noEmit

Expand All @@ -57,7 +60,7 @@ jobs:

- name: Upload test reports
if: always()
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: test-reports
if-no-files-found: ignore
Expand All @@ -72,10 +75,10 @@ jobs:

steps:
- name: Check out repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2

- name: Set up Node.js
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
with:
node-version: 24
cache: npm
Expand Down
11 changes: 7 additions & 4 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ jobs:
repositories: ttdash

- name: Check out repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
fetch-depth: 0
ref: main
Expand Down Expand Up @@ -99,7 +99,7 @@ jobs:
fi

- name: Set up Node.js
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
with:
node-version: 24
cache: npm
Expand Down Expand Up @@ -138,6 +138,9 @@ jobs:
- name: Run ESLint
run: npm run lint

- name: Run docstring lint
run: npm run lint:docstrings

- name: Run TypeScript checks
run: ./node_modules/.bin/tsc --noEmit

Expand All @@ -157,7 +160,7 @@ jobs:
run: npm run test:e2e:ci

- name: Set up Bun
uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2
uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0
with:
bun-version: 1.3.4

Expand All @@ -172,7 +175,7 @@ jobs:

- name: Load release signing secrets
id: load-release-secrets
uses: 1Password/load-secrets-action@dafbe7cb03502b260e2b2893c753c352eee545bf # v3
uses: 1Password/load-secrets-action@92467eb28f72e8255933372f1e0707c567ce2259 # v4.0.0
env:
OP_SERVICE_ACCOUNT_TOKEN: ${{ secrets.OP_SERVICE_ACCOUNT_TOKEN_PUBLIC }}
RELEASE_SIGNER_NAME: ${{ secrets.OP_SSH_BASE_URL }}name
Expand Down
9 changes: 9 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,12 @@ requirements/
/request-*.png
/requests-*.png
/ttdash-dashboard-*.png
/dashboard-*-review.png
/forecast-*-review.png
/help-*-review.png
/loaded-dashboard*.png
/mobile-*.png
/settings-*-review.png
/settings-empty.png
/tables-*-review.png
/empty-state.png
2 changes: 2 additions & 0 deletions .prettierrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
"semi": false,
"trailingComma": "all",
"printWidth": 100,
"tailwindStylesheet": "./src/index.css",
"plugins": ["prettier-plugin-tailwindcss"],
"overrides": [
{
"files": ["server.js", "usage-normalizer.js", "scripts/**/*.js", "server/**/*.js"],
Expand Down
27 changes: 27 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,32 @@
# Changelog

## [6.2.1] - 2026-04-15

### Added

- **Tiefere Drilldown-Analyse für `Letzte Tage`** — der Detaildialog zeigt jetzt deutlich mehr Tages- und Periodenkontext, darunter modellbezogene Kosten-, Token- und Request-Kennzahlen, Provider-Zusammenfassungen, Token-Verteilungen sowie Benchmarks gegen Vorperiode und Kurzzeitschnitt
- **Direkte Navigation im `Letzte Tage`-Drilldown** — innerhalb des geöffneten Detaildialogs kann jetzt direkt zum vorherigen oder nächsten Tag bzw. zur nächsten Periode gewechselt werden, inklusive Positionsanzeige und Pfeiltasten-Navigation
- **Verbindliche Docstring-Prüfung für Produktionscode** — kurze englische JSDoc-Kommentare werden jetzt für die öffentliche Produktionsoberfläche des Repos geprüft und lokal wie in CI/Release als eigener Gate mitgeführt

### Improved

- **Umfassende UI-Qualität im gesamten Dashboard** — Filter, Overlays, Toasts, Heatmaps, Tabellen, Karten und Diagrammflächen wurden nach einem tiefen UI-Review gezielt gehärtet, mit besserer Accessibility, klarerer Zustandskommunikation, stärkerer Mobile-Discoverability und konsistenteren Focus-/Zoom-Flows
- **Detailqualität und Aussagekraft der Dashboard-Ansichten** — Modell- und Provider-Informationen, Chart-Lesbarkeit, Light-/Dark-Parität, Filterstatus-Klarheit und mobile Header-/Legend-Darstellung wurden über mehrere Oberflächen hinweg präzisiert, ohne das bestehende Nutzungsmodell zu verändern
- **Performance auf Start-, Filter- und Großdatensatzpfaden** — der Dashboard-Root remountet bei normalen Filterwechseln nicht mehr unnötig, Bootstrap-Settings werden ohne sofortigen Doppel-Fetch wiederverwendet, zentrale Datenableitungen laufen gebündelter, und große Tabellen-/Sekundärflächen skalieren spürbar besser
- **Ladeverhalten und Chunking des Dashboards** — Settings, Help, Drilldown, Auto-Import und viele schwerere Analyse-Sektionen werden jetzt lazy geladen, wodurch der Initialpfad schlanker bleibt, bei unveränderter Funktionalität und mit gezielt angepasstem Animationsverhalten in einigen Dashboard-Sektionen
- **Lokale Runtime und Report-I/O** — Upload-, Settings- und PDF-/Report-Pfade blockieren den Event Loop weniger stark, weil mehrere synchrone Dateisystemoperationen auf asynchronere Verarbeitung umgestellt wurden
- **Absicherung für die Weiterentwicklung** — neue und erweiterte Frontend-, Hook-, Daten- und E2E-Tests decken die UI-, Drilldown- und Performance-Verbesserungen gezielt ab
- **Lint-, Format- und Test-Gates für die Weiterentwicklung** — React-, Accessibility-, Import-, Testing-Library-, jest-dom- und Playwright-Linting sowie Tailwind-Class-Sorting sind jetzt Teil der normalen lokalen und GitHub-Gates
- **Release- und Maintainer-Dokumentation** — `RELEASING.md` beschreibt jetzt Fehlerszenarien und Retry-Bedingungen klarer, einschließlich 1Password-URL-Validierung und GitHub-Domain-Verifikation
- **Workflow-Pins und Build-Tooling** — GitHub Actions sind auf aktuelle stabile Commit-Hashes samt präziser Versionskommentare gebracht, und kompatible Direktabhängigkeiten wie `react-i18next` und `typescript-eslint` wurden aktualisiert

### Fixed

- **Semantik und Bedienbarkeit zentraler Filter- und Overlay-Flächen** — Date-Picker, Filter-Chips, Info-Buttons und Toasts verhalten sich jetzt konsistenter für Keyboard-, Screenreader- und Touch-Nutzung
- **Bewegungs- und Diagrammverhalten in Dashboard-Sektionen** — doppelte oder unpassende Reveal-/Chart-Animationen, unvollständige Reduced-Motion-Pfade und mehrere Timing-/Discoverability-Probleme in expandierbaren Analyseflächen wurden bereinigt
- **Skalierungsprobleme in `Letzte Tage` und sekundären Oberflächen** — große Tabellenansichten, Help-/Settings-Öffnung und weitere schwere UI-Pfade reagieren unter größeren Datenmengen robuster als zuvor
- **Mehrere Review- und CI-Findings aus CodeRabbit und lokaler Validierung** — period-aware Drilldown-Benchmarks, Heatmap-Semantik, Cache-ROI-Vorzeichenlogik, Help-Dialog-Lifecycle, Weekday-Lokalisierung, testbezogene Timer-/RAF-Stabilität und race-sichere serverseitige Datei-Mutationen wurden gezielt bereinigt

## [6.2.0] - 2026-04-14

### Added
Expand Down
14 changes: 10 additions & 4 deletions RELEASING.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,10 @@ Before the first public release, configure npm Trusted Publishing for this repos
6. Install the `ttdash-release` GitHub App on `roastcodes/ttdash`
7. Add `APP_CLIENT_ID` and `APP_PRIVATE_KEY` as Actions secrets for this repository or the `release` environment
8. Add `OP_SERVICE_ACCOUNT_TOKEN_PUBLIC` as an Actions secret for this repository or the `release` environment
9. Add `OP_SSH_BASE_URL` as an Actions secret for this repository or the `release` environment, point it to the shared 1Password item prefix for the release signer, and make sure it ends with a trailing `/`
9. Add `OP_SSH_BASE_URL` as an Actions secret for this repository or the `release` environment, point it to the shared 1Password item prefix for the release signer, and make sure it ends with a trailing `/` (otherwise the workflow fails its format-validation step before it attempts to load secrets)
10. Add the `ttdash-release` GitHub App as a bypass actor in the `main` ruleset

The release workflow loads the SSH signing identity from 1Password through the public-repo service account token. `OP_SSH_BASE_URL` must contain only the common item prefix and must end with `/`, for example `op://vault/item/`, while the workflow appends `name`, `comment`, `public key`, and `private key?ssh-format=openssh` internally. The workflow validates this format before loading secrets. The SSH public key must remain added to the maintainer GitHub account as an SSH signing key, and the signing email used by the workflow must stay valid for both GitHub verification and the `roastcodes` organization trailer.
The release workflow loads the SSH signing identity from 1Password through the public-repo service account token. `OP_SSH_BASE_URL` must contain only the common item prefix and must end with `/`, for example `op://vault/item/`, while the workflow appends `name`, `comment`, `public key`, and `private key?ssh-format=openssh` internally. The workflow validates this format before loading secrets and exits early with a format error if the trailing slash is missing. The SSH public key must remain added to the maintainer GitHub account as an SSH signing key, and the signing email used by the workflow must stay valid for both GitHub verification and the `roastcodes` organization trailer.

Trusted Publishing is preferred because it avoids long-lived npm tokens and enables provenance for public publishes.

Expand All @@ -28,7 +28,7 @@ Before using the manual release workflow, make sure:
1. `main` is protected and requires the `CI` status check before merges
2. CodeQL is enabled in the GitHub UI if you want it as a manual release gate
3. the `ttdash-release` GitHub App is allowed to push the version-bump commit and signed tag back to `main`
4. the `roast.codes` domain remains verified for the `roastcodes` organization so the workflow-created `on-behalf-of: @roastcodes <github@roast.codes>` trailer continues to render correctly on GitHub
4. the `roast.codes` domain remains verified for the `roastcodes` organization so the workflow-created `on-behalf-of: @roastcodes <github@roast.codes>` trailer continues to render correctly on GitHub (check GitHub organization settings under verified domains; if verification lapses, restore or confirm the DNS TXT record and re-verify the domain as documented at https://docs.github.com/en/organizations/managing-organization-settings/verifying-or-approving-a-domain-for-your-organization)

If branch protection or rulesets block the `ttdash-release` app from writing to `main` or pushing `v*` tags, the workflow will fail when it tries to push the release commit or tag.

Expand Down Expand Up @@ -71,7 +71,13 @@ On a manual `workflow_dispatch` run against `main`, the workflow:
14. creates the GitHub release

Note: the workflow reruns the release-critical test suite itself after the version bump. This is necessary because the workflow-created push back to `main` should not be relied on to trigger the normal `CI` workflow again.
If a release fails after the version bump was already pushed, rerunning the workflow with the same version only resumes that release while `main` still points at the original `vX.Y.Z: Release` commit. Retry mode also requires any pre-existing `vX.Y.Z` tag to already be signed and to point at that same release commit. If new commits landed on `main` in the meantime, or an existing tag does not match the release commit, the workflow now aborts early and you should cut a new version instead of retrying the old one.
If a release fails after the version bump was already pushed, rerunning the workflow with the same version resumes that release only when all retry conditions still hold:

- `main` still points at the original `vX.Y.Z: Release` commit
- any pre-existing `vX.Y.Z` tag is already signed
- any pre-existing `vX.Y.Z` tag points at that same release commit

If new commits landed on `main` in the meantime, or an existing tag does not match the release commit, the workflow aborts early and you should cut a new version instead of retrying the old one.

## Post-Publish Checks

Expand Down
188 changes: 188 additions & 0 deletions eslint.config.mjs
Original file line number Diff line number Diff line change
@@ -1,7 +1,15 @@
import { defineConfig } from 'eslint/config'
import js from '@eslint/js'
import eslintConfigPrettier from 'eslint-config-prettier'
import { createTypeScriptImportResolver } from 'eslint-import-resolver-typescript'
import importPlugin from 'eslint-plugin-import-x'
import jestDom from 'eslint-plugin-jest-dom'
import jsdoc from 'eslint-plugin-jsdoc'
import jsxA11y from 'eslint-plugin-jsx-a11y'
import playwright from 'eslint-plugin-playwright'
import react from 'eslint-plugin-react'
import reactHooks from 'eslint-plugin-react-hooks'
import testingLibrary from 'eslint-plugin-testing-library'
import globals from 'globals'
import tseslint from 'typescript-eslint'

Expand Down Expand Up @@ -92,5 +100,185 @@ export default defineConfig(
'@typescript-eslint/switch-exhaustiveness-check': 'error',
},
},
{
files: ['src/**/*.{tsx,jsx}'],
extends: [react.configs.flat.recommended, react.configs.flat['jsx-runtime']],
languageOptions: {
parserOptions: {
ecmaFeatures: {
jsx: true,
},
},
},
settings: {
react: {
version: 'detect',
},
},
},
{
files: ['src/**/*.{tsx,jsx}'],
extends: [jsxA11y.flatConfigs.recommended],
languageOptions: {
parserOptions: {
ecmaFeatures: {
jsx: true,
},
},
},
},
{
files: ['tests/frontend/**/*.test.tsx'],
extends: [testingLibrary.configs['flat/react'], jestDom.configs['flat/recommended']],
rules: {
'testing-library/no-container': 'off',
'testing-library/no-node-access': 'off',
},
},
{
files: ['tests/e2e/**/*.ts'],
extends: [playwright.configs['flat/recommended']],
},
{
files: ['**/*.{js,cjs,mjs,ts,tsx}'],
extends: [importPlugin.flatConfigs.recommended, importPlugin.flatConfigs.typescript],
languageOptions: {
ecmaVersion: 'latest',
},
settings: {
'import-x/resolver-next': [
createTypeScriptImportResolver({
alwaysTryTypes: true,
project: './tsconfig.json',
}),
],
},
rules: {
'import-x/export': 'error',
'import-x/first': 'error',
'import-x/newline-after-import': 'error',
'import-x/no-duplicates': 'error',
'import-x/no-named-as-default': 'off',
'import-x/no-named-as-default-member': 'off',
'import-x/no-unresolved': 'error',
},
},
{
files: ['src/**/*.{ts,tsx}', 'shared/**/*.d.ts'],
plugins: {
jsdoc,
},
settings: {
jsdoc: {
mode: 'typescript',
},
},
rules: {
'jsdoc/check-alignment': 'error',
'jsdoc/check-param-names': 'error',
'jsdoc/check-property-names': 'error',
'jsdoc/check-syntax': 'error',
'jsdoc/check-tag-names': 'error',
'jsdoc/empty-tags': 'error',
'jsdoc/no-types': 'error',
'jsdoc/require-description': [
'error',
{
checkConstructors: false,
contexts: [
'TSInterfaceDeclaration',
'TSTypeAliasDeclaration',
'FunctionDeclaration',
'VariableDeclaration',
'ClassDeclaration',
],
descriptionStyle: 'body',
},
],
'jsdoc/require-description-complete-sentence': 'error',
'jsdoc/require-hyphen-before-param-description': ['error', 'always'],
'jsdoc/require-jsdoc': [
'error',
{
checkConstructors: false,
checkGetters: false,
checkSetters: false,
contexts: [
'ExportNamedDeclaration > TSInterfaceDeclaration',
'ExportNamedDeclaration > TSTypeAliasDeclaration',
'ExportNamedDeclaration > VariableDeclaration',
],
publicOnly: {
ancestorsOnly: true,
esm: true,
},
require: {
ArrowFunctionExpression: false,
ClassDeclaration: true,
FunctionDeclaration: true,
FunctionExpression: false,
MethodDefinition: false,
},
},
],
'jsdoc/require-param-type': 'off',
'jsdoc/require-returns-type': 'off',
'jsdoc/sort-tags': 'error',
},
},
{
files: ['shared/**/*.js', 'server/**/*.js', 'server.js', 'usage-normalizer.js'],
plugins: {
jsdoc,
},
settings: {
jsdoc: {
mode: 'typescript',
},
},
rules: {
'jsdoc/check-alignment': 'error',
'jsdoc/check-param-names': 'error',
'jsdoc/check-property-names': 'error',
'jsdoc/check-syntax': 'error',
'jsdoc/check-tag-names': 'error',
'jsdoc/empty-tags': 'error',
'jsdoc/no-types': 'error',
'jsdoc/require-description': [
'error',
{
checkConstructors: false,
contexts: ['FunctionDeclaration', 'VariableDeclaration'],
descriptionStyle: 'body',
},
],
'jsdoc/require-description-complete-sentence': 'error',
'jsdoc/require-hyphen-before-param-description': ['error', 'always'],
'jsdoc/require-jsdoc': [
'error',
{
checkConstructors: false,
checkGetters: false,
checkSetters: false,
contexts: ['ExportNamedDeclaration > VariableDeclaration'],
publicOnly: {
ancestorsOnly: true,
cjs: true,
esm: true,
},
require: {
ArrowFunctionExpression: false,
ClassDeclaration: true,
FunctionDeclaration: true,
FunctionExpression: false,
MethodDefinition: false,
},
},
],
'jsdoc/require-param-type': 'off',
'jsdoc/require-returns-type': 'off',
'jsdoc/sort-tags': 'error',
},
},
eslintConfigPrettier,
)
Loading
Loading