diff --git a/.github/workflows/codecov-upload.yml b/.github/workflows/codecov-upload.yml index d16b2192..8e6decec 100644 --- a/.github/workflows/codecov-upload.yml +++ b/.github/workflows/codecov-upload.yml @@ -35,7 +35,7 @@ jobs: exit ${PIPESTATUS[0]} - name: Upload backend coverage to Codecov - uses: codecov/codecov-action@5a1091511ad55cbe89839c7260b706298ca349f7 # v5 + uses: codecov/codecov-action@671740ac38dd9b0130fbe1cec585b89eea48d3de # v5 with: token: ${{ secrets.CODECOV_TOKEN }} files: ./backend/coverage.txt @@ -54,7 +54,7 @@ jobs: - name: Set up Node.js uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6 with: - node-version: '24.11.1' + node-version: '24.12.0' cache: 'npm' cache-dependency-path: frontend/package-lock.json @@ -69,7 +69,7 @@ jobs: exit ${PIPESTATUS[0]} - name: Upload frontend coverage to Codecov - uses: codecov/codecov-action@5a1091511ad55cbe89839c7260b706298ca349f7 # v5 + uses: codecov/codecov-action@671740ac38dd9b0130fbe1cec585b89eea48d3de # v5 with: token: ${{ secrets.CODECOV_TOKEN }} directory: ./frontend/coverage diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 8194f3f0..2f36963f 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -34,7 +34,7 @@ jobs: uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6 - name: Initialize CodeQL - uses: github/codeql-action/init@cf1bb45a277cb3c205638b2cd5c984db1c46a412 # v4 + uses: github/codeql-action/init@1b168cd39490f61582a9beae412bb7057a6b2c4e # v4 with: languages: ${{ matrix.language }} @@ -45,9 +45,9 @@ jobs: go-version: '1.25.5' - name: Autobuild - uses: github/codeql-action/autobuild@cf1bb45a277cb3c205638b2cd5c984db1c46a412 # v4 + uses: github/codeql-action/autobuild@1b168cd39490f61582a9beae412bb7057a6b2c4e # v4 - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@cf1bb45a277cb3c205638b2cd5c984db1c46a412 # v4 + uses: github/codeql-action/analyze@1b168cd39490f61582a9beae412bb7057a6b2c4e # v4 with: category: "/language:${{ matrix.language }}" diff --git a/.github/workflows/docker-build.yml b/.github/workflows/docker-build.yml index 66bf4376..3235fc61 100644 --- a/.github/workflows/docker-build.yml +++ b/.github/workflows/docker-build.yml @@ -151,7 +151,7 @@ jobs: - name: Upload Trivy results if: github.event_name != 'pull_request' && steps.skip.outputs.skip_build != 'true' && steps.trivy-check.outputs.exists == 'true' - uses: github/codeql-action/upload-sarif@cf1bb45a277cb3c205638b2cd5c984db1c46a412 # v4.31.7 + uses: github/codeql-action/upload-sarif@1b168cd39490f61582a9beae412bb7057a6b2c4e # v4.31.8 with: sarif_file: 'trivy-results.sarif' token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/docker-publish.yml b/.github/workflows/docker-publish.yml index c6aded05..f3837577 100644 --- a/.github/workflows/docker-publish.yml +++ b/.github/workflows/docker-publish.yml @@ -155,7 +155,7 @@ jobs: - name: Upload Trivy results if: github.event_name != 'pull_request' && steps.skip.outputs.skip_build != 'true' && steps.trivy-check.outputs.exists == 'true' - uses: github/codeql-action/upload-sarif@cf1bb45a277cb3c205638b2cd5c984db1c46a412 # v4.31.7 + uses: github/codeql-action/upload-sarif@1b168cd39490f61582a9beae412bb7057a6b2c4e # v4.31.8 with: sarif_file: 'trivy-results.sarif' token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/docs-to-issues.yml b/.github/workflows/docs-to-issues.yml new file mode 100644 index 00000000..a69d2355 --- /dev/null +++ b/.github/workflows/docs-to-issues.yml @@ -0,0 +1,369 @@ +name: Convert Docs to Issues + +on: + push: + branches: + - main + - development + paths: + - 'docs/issues/**/*.md' + - '!docs/issues/created/**' + - '!docs/issues/_TEMPLATE.md' + - '!docs/issues/README.md' + + # Allow manual trigger + workflow_dispatch: + inputs: + dry_run: + description: 'Dry run (no issues created)' + required: false + default: 'false' + type: boolean + file_path: + description: 'Specific file to process (optional)' + required: false + type: string + +permissions: + contents: write + issues: write + pull-requests: write + +jobs: + convert-docs: + name: Convert Markdown to Issues + runs-on: ubuntu-latest + if: github.actor != 'github-actions[bot]' + + steps: + - name: Checkout repository + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 + with: + fetch-depth: 2 + + - name: Set up Node.js + uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4 + with: + node-version: '20' + + - name: Install dependencies + run: npm install gray-matter + + - name: Detect changed files + id: changes + uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7 + with: + script: | + const fs = require('fs'); + const path = require('path'); + + // Manual file specification + const manualFile = '${{ github.event.inputs.file_path }}'; + if (manualFile) { + if (fs.existsSync(manualFile)) { + core.setOutput('files', JSON.stringify([manualFile])); + return; + } else { + core.setFailed(`File not found: ${manualFile}`); + return; + } + } + + // Get changed files from commit + const { data: commit } = await github.rest.repos.getCommit({ + owner: context.repo.owner, + repo: context.repo.repo, + ref: context.sha + }); + + const changedFiles = (commit.files || []) + .filter(f => f.filename.startsWith('docs/issues/')) + .filter(f => !f.filename.startsWith('docs/issues/created/')) + .filter(f => !f.filename.includes('_TEMPLATE')) + .filter(f => !f.filename.includes('README')) + .filter(f => f.filename.endsWith('.md')) + .filter(f => f.status !== 'removed') + .map(f => f.filename); + + console.log('Changed issue files:', changedFiles); + core.setOutput('files', JSON.stringify(changedFiles)); + + - name: Process issue files + id: process + uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7 + env: + DRY_RUN: ${{ github.event.inputs.dry_run || 'false' }} + with: + script: | + const fs = require('fs'); + const path = require('path'); + const matter = require('gray-matter'); + + const files = JSON.parse('${{ steps.changes.outputs.files }}'); + const isDryRun = process.env.DRY_RUN === 'true'; + const createdIssues = []; + const errors = []; + + if (files.length === 0) { + console.log('No issue files to process'); + core.setOutput('created_count', 0); + core.setOutput('created_issues', '[]'); + core.setOutput('errors', '[]'); + return; + } + + // Label color map + const labelColors = { + testing: 'BFD4F2', + feature: 'A2EEEF', + enhancement: '84B6EB', + bug: 'D73A4A', + documentation: '0075CA', + backend: '1D76DB', + frontend: '5EBEFF', + security: 'EE0701', + ui: '7057FF', + caddy: '1F6FEB', + 'needs-triage': 'FBCA04', + acl: 'C5DEF5', + regression: 'D93F0B', + 'manual-testing': 'BFD4F2', + 'bulk-acl': '006B75', + 'error-handling': 'D93F0B', + 'ui-ux': '7057FF', + integration: '0E8A16', + performance: 'EDEDED', + 'cross-browser': '5319E7', + plus: 'FFD700', + beta: '0052CC', + alpha: '5319E7', + high: 'D93F0B', + medium: 'FBCA04', + low: '0E8A16', + critical: 'B60205', + architecture: '006B75', + database: '006B75', + 'post-beta': '006B75' + }; + + // Helper: Ensure label exists + async function ensureLabel(name) { + try { + await github.rest.issues.getLabel({ + owner: context.repo.owner, + repo: context.repo.repo, + name: name + }); + } catch (e) { + if (e.status === 404) { + await github.rest.issues.createLabel({ + owner: context.repo.owner, + repo: context.repo.repo, + name: name, + color: labelColors[name.toLowerCase()] || '666666' + }); + console.log(`Created label: ${name}`); + } + } + } + + // Helper: Parse markdown file + function parseIssueFile(filePath) { + const content = fs.readFileSync(filePath, 'utf8'); + const { data: frontmatter, content: body } = matter(content); + + // Extract title: frontmatter > first H1 > filename + let title = frontmatter.title; + if (!title) { + const h1Match = body.match(/^#\s+(.+)$/m); + title = h1Match ? h1Match[1] : path.basename(filePath, '.md').replace(/-/g, ' '); + } + + // Build labels array + const labels = [...(frontmatter.labels || [])]; + if (frontmatter.priority) labels.push(frontmatter.priority); + if (frontmatter.type) labels.push(frontmatter.type); + + return { + title, + body: body.trim(), + labels: [...new Set(labels)], + assignees: frontmatter.assignees || [], + milestone: frontmatter.milestone, + parent_issue: frontmatter.parent_issue, + create_sub_issues: frontmatter.create_sub_issues || false + }; + } + + // Helper: Extract sub-issues from H2 sections + function extractSubIssues(body, parentLabels) { + const sections = []; + const lines = body.split('\n'); + let currentSection = null; + let currentBody = []; + + for (const line of lines) { + const h2Match = line.match(/^##\s+(?:Sub-Issue\s*#?\d*:?\s*)?(.+)$/); + if (h2Match) { + if (currentSection) { + sections.push({ + title: currentSection, + body: currentBody.join('\n').trim(), + labels: [...parentLabels] + }); + } + currentSection = h2Match[1].trim(); + currentBody = []; + } else if (currentSection) { + currentBody.push(line); + } + } + + if (currentSection) { + sections.push({ + title: currentSection, + body: currentBody.join('\n').trim(), + labels: [...parentLabels] + }); + } + + return sections; + } + + // Process each file + for (const filePath of files) { + console.log(`\nProcessing: ${filePath}`); + + try { + const parsed = parseIssueFile(filePath); + console.log(` Title: ${parsed.title}`); + console.log(` Labels: ${parsed.labels.join(', ')}`); + + if (isDryRun) { + console.log(' [DRY RUN] Would create issue'); + createdIssues.push({ file: filePath, title: parsed.title, dryRun: true }); + continue; + } + + // Ensure labels exist + for (const label of parsed.labels) { + await ensureLabel(label); + } + + // Create the main issue + const issueBody = parsed.body + + `\n\n---\n*Auto-created from [${path.basename(filePath)}](https://github.com/${context.repo.owner}/${context.repo.repo}/blob/${context.sha}/${filePath})*`; + + const issueResponse = await github.rest.issues.create({ + owner: context.repo.owner, + repo: context.repo.repo, + title: parsed.title, + body: issueBody, + labels: parsed.labels, + assignees: parsed.assignees + }); + + const issueNumber = issueResponse.data.number; + console.log(` Created issue #${issueNumber}`); + + // Handle sub-issues + if (parsed.create_sub_issues) { + const subIssues = extractSubIssues(parsed.body, parsed.labels); + for (const sub of subIssues) { + for (const label of sub.labels) { + await ensureLabel(label); + } + const subResponse = await github.rest.issues.create({ + owner: context.repo.owner, + repo: context.repo.repo, + title: `[${parsed.title}] ${sub.title}`, + body: sub.body + `\n\n---\n*Sub-issue of #${issueNumber}*`, + labels: sub.labels, + assignees: parsed.assignees + }); + console.log(` Created sub-issue #${subResponse.data.number}: ${sub.title}`); + } + } + + // Link to parent issue if specified + if (parsed.parent_issue) { + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: parsed.parent_issue, + body: `Sub-issue created: #${issueNumber}` + }); + } + + createdIssues.push({ + file: filePath, + title: parsed.title, + issueNumber + }); + + } catch (error) { + console.error(` Error processing ${filePath}: ${error.message}`); + errors.push({ file: filePath, error: error.message }); + } + } + + core.setOutput('created_count', createdIssues.length); + core.setOutput('created_issues', JSON.stringify(createdIssues)); + core.setOutput('errors', JSON.stringify(errors)); + + if (errors.length > 0) { + core.warning(`${errors.length} file(s) had errors`); + } + + - name: Move processed files + if: steps.process.outputs.created_count != '0' && github.event.inputs.dry_run != 'true' + run: | + mkdir -p docs/issues/created + CREATED_ISSUES='${{ steps.process.outputs.created_issues }}' + echo "$CREATED_ISSUES" | jq -r '.[].file' | while read file; do + if [ -f "$file" ] && [ ! -z "$file" ]; then + filename=$(basename "$file") + timestamp=$(date +%Y%m%d) + mv "$file" "docs/issues/created/${timestamp}-${filename}" + echo "Moved: $file -> docs/issues/created/${timestamp}-${filename}" + fi + done + + - name: Commit moved files + if: steps.process.outputs.created_count != '0' && github.event.inputs.dry_run != 'true' + run: | + git config --local user.email "github-actions[bot]@users.noreply.github.com" + git config --local user.name "github-actions[bot]" + git add docs/issues/ + git diff --staged --quiet || git commit -m "chore: move processed issue files to created/ [skip ci]" + git push + + - name: Summary + if: always() + run: | + echo "## Docs to Issues Summary" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + + CREATED='${{ steps.process.outputs.created_issues }}' + ERRORS='${{ steps.process.outputs.errors }}' + DRY_RUN='${{ github.event.inputs.dry_run }}' + + if [ "$DRY_RUN" = "true" ]; then + echo "🔍 **Dry Run Mode** - No issues were actually created" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + fi + + echo "### Created Issues" >> $GITHUB_STEP_SUMMARY + if [ -n "$CREATED" ] && [ "$CREATED" != "[]" ] && [ "$CREATED" != "null" ]; then + echo "$CREATED" | jq -r '.[] | "- \(.title) (#\(.issueNumber // "dry-run"))"' >> $GITHUB_STEP_SUMMARY || echo "_Parse error_" >> $GITHUB_STEP_SUMMARY + else + echo "_No issues created_" >> $GITHUB_STEP_SUMMARY + fi + + echo "" >> $GITHUB_STEP_SUMMARY + echo "### Errors" >> $GITHUB_STEP_SUMMARY + if [ -n "$ERRORS" ] && [ "$ERRORS" != "[]" ] && [ "$ERRORS" != "null" ]; then + echo "$ERRORS" | jq -r '.[] | "- ❌ \(.file): \(.error)"' >> $GITHUB_STEP_SUMMARY || echo "_Parse error_" >> $GITHUB_STEP_SUMMARY + else + echo "_No errors_" >> $GITHUB_STEP_SUMMARY + fi diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 3e1366ec..97901097 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -35,7 +35,7 @@ jobs: - name: 🔧 Set up Node.js uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6 with: - node-version: '24.11.1' + node-version: '24.12.0' # Step 3: Create a beautiful docs site structure - name: 📝 Build documentation site diff --git a/.github/workflows/propagate-changes.yml b/.github/workflows/propagate-changes.yml index 1a193bc8..de3b3b4d 100644 --- a/.github/workflows/propagate-changes.yml +++ b/.github/workflows/propagate-changes.yml @@ -20,7 +20,7 @@ jobs: - name: Set up Node (for github-script) uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6 with: - node-version: '24.11.1' + node-version: '24.12.0' - name: Propagate Changes uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 diff --git a/.github/workflows/quality-checks.yml b/.github/workflows/quality-checks.yml index c2d98376..1b6ba5ee 100644 --- a/.github/workflows/quality-checks.yml +++ b/.github/workflows/quality-checks.yml @@ -89,7 +89,7 @@ jobs: - name: Set up Node.js uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0 with: - node-version: '24.11.1' + node-version: '24.12.0' cache: 'npm' cache-dependency-path: frontend/package-lock.json diff --git a/.github/workflows/release-goreleaser.yml b/.github/workflows/release-goreleaser.yml index c9068e89..d19f1329 100644 --- a/.github/workflows/release-goreleaser.yml +++ b/.github/workflows/release-goreleaser.yml @@ -31,7 +31,7 @@ jobs: - name: Set up Node.js uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6 with: - node-version: '24.11.1' + node-version: '24.12.0' - name: Build Frontend working-directory: frontend diff --git a/.github/workflows/renovate.yml b/.github/workflows/renovate.yml index eb3b5a36..efab03ed 100644 --- a/.github/workflows/renovate.yml +++ b/.github/workflows/renovate.yml @@ -37,7 +37,7 @@ jobs: fi - name: Run Renovate - uses: renovatebot/github-action@5712c6a41dea6cdf32c72d92a763bd417e6606aa # v44.0.5 + uses: renovatebot/github-action@502904f1cefdd70cba026cb1cbd8c53a1443e91b # v44.1.0 with: configurationFile: .github/renovate.json token: ${{ env.GITHUB_TOKEN }} diff --git a/.github/workflows/repo-health.yml b/.github/workflows/repo-health.yml index 462d2021..93d17dd6 100644 --- a/.github/workflows/repo-health.yml +++ b/.github/workflows/repo-health.yml @@ -32,7 +32,7 @@ jobs: - name: Upload health output if: always() - uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 + uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6 with: name: repo-health-output path: | diff --git a/Dockerfile b/Dockerfile index 5009e47d..e2f3ea38 100644 --- a/Dockerfile +++ b/Dockerfile @@ -25,7 +25,7 @@ FROM --platform=$BUILDPLATFORM tonistiigi/xx:1.9.0 AS xx # ---- Frontend Builder ---- # Build the frontend using the BUILDPLATFORM to avoid arm64 musl Rollup native issues -FROM --platform=$BUILDPLATFORM node:24.11.1-alpine AS frontend-builder +FROM --platform=$BUILDPLATFORM node:24.12.0-alpine AS frontend-builder WORKDIR /app/frontend # Copy frontend package files diff --git a/docs/issues/README.md b/docs/issues/README.md new file mode 100644 index 00000000..41feb422 --- /dev/null +++ b/docs/issues/README.md @@ -0,0 +1,85 @@ +# docs/issues - Issue Specification Files + +This directory contains markdown files that are automatically converted to GitHub Issues when merged to `main` or `development`. + +## How It Works + +1. **Create a markdown file** in this directory using the template format +2. **Add YAML frontmatter** with issue metadata (title, labels, priority, etc.) +3. **Merge to main/development** - the `docs-to-issues.yml` workflow runs +4. **GitHub Issue is created** with your specified metadata +5. **File is moved** to `docs/issues/created/` to prevent duplicates + +## Quick Start + +Copy `_TEMPLATE.md` and fill in your issue details: + +```yaml +--- +title: "My New Issue" +labels: + - feature + - backend +priority: medium +--- + +# My New Issue + +Description of the issue... +``` + +## Frontmatter Fields + +| Field | Required | Description | +|-------|----------|-------------| +| `title` | Yes* | Issue title (*or uses first H1 as fallback) | +| `labels` | No | Array of labels to apply | +| `priority` | No | `critical`, `high`, `medium`, `low` | +| `milestone` | No | Milestone name | +| `assignees` | No | Array of GitHub usernames | +| `parent_issue` | No | Parent issue number for linking | +| `create_sub_issues` | No | If `true`, each `## Section` becomes a sub-issue | + +## Sub-Issues + +To create multiple related issues from one file, set `create_sub_issues: true`: + +```yaml +--- +title: "Main Testing Issue" +labels: [testing] +create_sub_issues: true +--- + +# Main Testing Issue + +Overview content for the parent issue. + +## Unit Testing + +This section becomes a separate issue. + +## Integration Testing + +This section becomes another separate issue. +``` + +## Manual Trigger + +You can manually run the workflow with: + +```bash +# Dry run (no issues created) +gh workflow run docs-to-issues.yml -f dry_run=true + +# Process specific file +gh workflow run docs-to-issues.yml -f file_path=docs/issues/my-issue.md +``` + +## Labels + +Labels are automatically created if they don't exist. Common labels: + +- **Priority**: `critical`, `high`, `medium`, `low` +- **Type**: `feature`, `bug`, `enhancement`, `testing`, `documentation` +- **Component**: `backend`, `frontend`, `ui`, `security`, `caddy`, `database` diff --git a/docs/issues/_TEMPLATE.md b/docs/issues/_TEMPLATE.md new file mode 100644 index 00000000..462b79fd --- /dev/null +++ b/docs/issues/_TEMPLATE.md @@ -0,0 +1,45 @@ +--- +# REQUIRED: Issue title +title: "Your Issue Title" + +# OPTIONAL: Labels to apply (will be created if missing) +labels: + - feature # feature, bug, enhancement, testing, documentation + - backend # backend, frontend, ui, security, caddy, database + +# OPTIONAL: Priority (creates matching label) +priority: medium # critical, high, medium, low + +# OPTIONAL: Milestone name +milestone: "v0.2.0-beta.2" + +# OPTIONAL: GitHub usernames to assign +assignees: [] + +# OPTIONAL: Parent issue number for linking +# parent_issue: 42 + +# OPTIONAL: Parse ## sections as separate sub-issues +# create_sub_issues: true +--- + +# Issue Title + +## Description + +Clear description of the issue or feature request. + +## Tasks + +- [ ] Task 1 +- [ ] Task 2 +- [ ] Task 3 + +## Acceptance Criteria + +- [ ] Criterion 1 +- [ ] Criterion 2 + +## Related Issues + +- #XX - Related issue description diff --git a/docs/issues/created/.gitkeep b/docs/issues/created/.gitkeep new file mode 100644 index 00000000..92e80370 --- /dev/null +++ b/docs/issues/created/.gitkeep @@ -0,0 +1 @@ +# Processed issue files are moved here after GitHub Issues are created diff --git a/docs/issues/ACL-testing-tasks.md b/docs/issues/created/20251213-ACL-testing-tasks.md similarity index 100% rename from docs/issues/ACL-testing-tasks.md rename to docs/issues/created/20251213-ACL-testing-tasks.md diff --git a/docs/issues/Additional_Security.md b/docs/issues/created/20251213-Additional_Security.md similarity index 100% rename from docs/issues/Additional_Security.md rename to docs/issues/created/20251213-Additional_Security.md diff --git a/docs/issues/bulk-acl-subissues.md b/docs/issues/created/20251213-bulk-acl-subissues.md similarity index 100% rename from docs/issues/bulk-acl-subissues.md rename to docs/issues/created/20251213-bulk-acl-subissues.md diff --git a/docs/issues/bulk-acl-testing.md b/docs/issues/created/20251213-bulk-acl-testing.md similarity index 100% rename from docs/issues/bulk-acl-testing.md rename to docs/issues/created/20251213-bulk-acl-testing.md diff --git a/docs/issues/hectate.md b/docs/issues/created/20251213-hectate.md similarity index 100% rename from docs/issues/hectate.md rename to docs/issues/created/20251213-hectate.md diff --git a/docs/issues/orthrus.md b/docs/issues/created/20251213-orthrus.md similarity index 100% rename from docs/issues/orthrus.md rename to docs/issues/created/20251213-orthrus.md diff --git a/docs/issues/plex-remote-access-helper.md b/docs/issues/created/20251213-plex-remote-access-helper.md similarity index 100% rename from docs/issues/plex-remote-access-helper.md rename to docs/issues/created/20251213-plex-remote-access-helper.md diff --git a/docs/issues/rotating-loading-animations.md b/docs/issues/created/20251213-rotating-loading-animations.md similarity index 100% rename from docs/issues/rotating-loading-animations.md rename to docs/issues/created/20251213-rotating-loading-animations.md diff --git a/docs/plans/docs_to_issues_workflow.md b/docs/plans/docs_to_issues_workflow.md new file mode 100644 index 00000000..3b3a96c4 --- /dev/null +++ b/docs/plans/docs_to_issues_workflow.md @@ -0,0 +1,910 @@ +# docs/issues to GitHub Issues Workflow - Implementation Plan + +**Version:** 1.0 +**Date:** 2025-12-13 +**Status:** 📋 PLANNING + +--- + +## Executive Summary + +This document provides a comprehensive plan for a GitHub Actions workflow that automatically converts markdown files in `docs/issues/` into GitHub Issues, applies labels, and adds them to the project board. + +--- + +## 1. Research Findings + +### 1.1 Current docs/issues File Analysis + +Analyzed 8 existing files in `docs/issues/`: + +| File | Structure | Has Frontmatter | Labels Present | Title Pattern | +|------|-----------|-----------------|----------------|---------------| +| `ACL-testing-tasks.md` | Free-form + metadata section | ❌ | Inline suggestion | H1 + metadata block | +| `Additional_Security.md` | Markdown lists | ❌ | ❌ | H3 title | +| `bulk-acl-subissues.md` | Multi-issue spec | ❌ | Inline per section | Inline titles | +| `bulk-acl-testing.md` | Full issue template | ❌ | Inline section | H1 title | +| `hectate.md` | Feature spec | ❌ | ❌ | H1 title | +| `orthrus.md` | Feature spec | ❌ | ❌ | H1 title | +| `plex-remote-access-helper.md` | Issue template | ❌ | Inline section | `## Issue Title` | +| `rotating-loading-animations.md` | Enhancement spec | ❌ | `**Issue Type**` line | H1 title | + +**Key Finding:** No files use YAML frontmatter. Most have ad-hoc metadata embedded in markdown body. + +### 1.2 Existing Workflow Patterns + +From `.github/workflows/`: + +| Workflow | Pattern | Relevance | +|----------|---------|-----------| +| `auto-label-issues.yml` | `actions/github-script` for issue manipulation | Label creation, issue API | +| `propagate-changes.yml` | Complex `github-script` with file analysis | File path detection | +| `auto-add-to-project.yml` | `actions/add-to-project` action | Project board integration | +| `create-labels.yml` | Label creation/update logic | Label management | + +### 1.3 Project Board Configuration + +- Project URL stored in `secrets.PROJECT_URL` +- PAT token: `secrets.ADD_TO_PROJECT_PAT` +- Uses `actions/add-to-project@v1.0.2` for automatic addition + +--- + +## 2. Markdown File Format Specification + +### 2.1 Required Frontmatter Format + +All files in `docs/issues/` should use YAML frontmatter: + +```yaml +--- +title: "Issue Title Here" +labels: + - testing + - feature + - backend +assignees: + - username1 + - username2 +milestone: "v0.2.0-beta.2" +priority: high # critical, high, medium, low +type: feature # feature, bug, enhancement, testing, documentation +parent_issue: 42 # Optional: Link as sub-issue +create_sub_issues: true # Optional: Parse ## sections as separate issues +--- + +# Issue Title (H1 fallback if frontmatter title missing) + +Issue body content starts here... +``` + +### 2.2 Frontmatter Fields + +| Field | Required | Type | Description | +|-------|----------|------|-------------| +| `title` | Yes* | string | Issue title (*or H1 fallback) | +| `labels` | No | array | Labels to apply (created if missing) | +| `assignees` | No | array | GitHub usernames | +| `milestone` | No | string | Milestone name | +| `priority` | No | enum | Maps to priority label | +| `type` | No | enum | Maps to type label | +| `parent_issue` | No | number | Parent issue number for linking | +| `create_sub_issues` | No | boolean | Parse H2 sections as sub-issues | + +### 2.3 Sub-Issue Detection + +When `create_sub_issues: true`, each `## Section Title` becomes a sub-issue: + +```markdown +--- +title: "Main Testing Issue" +labels: [testing] +create_sub_issues: true +--- + +# Main Testing Issue + +Overview text (goes in parent issue). + +## Sub-Issue #1: Unit Testing + +This section becomes a separate issue titled "Unit Testing" +with body content from this section. + +## Sub-Issue #2: Integration Testing + +This section becomes another separate issue. +``` + +--- + +## 3. Workflow Implementation + +### 3.1 Workflow File: `.github/workflows/docs-to-issues.yml` + +```yaml +name: Convert Docs to Issues + +on: + push: + branches: + - main + - development + paths: + - 'docs/issues/**/*.md' + - '!docs/issues/created/**' + + # Allow manual trigger + workflow_dispatch: + inputs: + dry_run: + description: 'Dry run (no issues created)' + required: false + default: 'false' + type: boolean + file_path: + description: 'Specific file to process (optional)' + required: false + type: string + +permissions: + contents: write + issues: write + pull-requests: write + +jobs: + convert-docs: + name: Convert Markdown to Issues + runs-on: ubuntu-latest + if: github.actor != 'github-actions[bot]' + + steps: + - name: Checkout repository + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 + with: + fetch-depth: 2 # Need previous commit for diff + + - name: Set up Node.js + uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4 + with: + node-version: '20' + + - name: Install dependencies + run: npm install gray-matter + + - name: Detect changed files + id: changes + uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7 + with: + script: | + const fs = require('fs'); + const path = require('path'); + + // Manual file specification + const manualFile = '${{ github.event.inputs.file_path }}'; + if (manualFile) { + if (fs.existsSync(manualFile)) { + core.setOutput('files', JSON.stringify([manualFile])); + return; + } else { + core.setFailed(`File not found: ${manualFile}`); + return; + } + } + + // Get changed files from commit + const { data: commit } = await github.rest.repos.getCommit({ + owner: context.repo.owner, + repo: context.repo.repo, + ref: context.sha + }); + + const changedFiles = (commit.files || []) + .filter(f => f.filename.startsWith('docs/issues/')) + .filter(f => !f.filename.startsWith('docs/issues/created/')) + .filter(f => f.filename.endsWith('.md')) + .filter(f => f.status !== 'removed') + .map(f => f.filename); + + console.log('Changed issue files:', changedFiles); + core.setOutput('files', JSON.stringify(changedFiles)); + + - name: Process issue files + id: process + uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7 + env: + DRY_RUN: ${{ github.event.inputs.dry_run || 'false' }} + with: + script: | + const fs = require('fs'); + const path = require('path'); + const matter = require('gray-matter'); + + const files = JSON.parse('${{ steps.changes.outputs.files }}'); + const isDryRun = process.env.DRY_RUN === 'true'; + const createdIssues = []; + const errors = []; + + if (files.length === 0) { + console.log('No issue files to process'); + core.setOutput('created_count', 0); + return; + } + + // Helper: Ensure label exists + async function ensureLabel(name) { + try { + await github.rest.issues.getLabel({ + owner: context.repo.owner, + repo: context.repo.repo, + name: name + }); + } catch (e) { + if (e.status === 404) { + // Create label with default color + const colors = { + testing: 'BFD4F2', + feature: 'A2EEEF', + enhancement: '84B6EB', + bug: 'D73A4A', + documentation: '0075CA', + backend: '1D76DB', + frontend: '5EBEFF', + security: 'EE0701', + ui: '7057FF', + caddy: '1F6FEB', + 'needs-triage': 'FBCA04', + acl: 'C5DEF5', + regression: 'D93F0B', + 'manual-testing': 'BFD4F2', + 'bulk-acl': '006B75', + 'error-handling': 'D93F0B', + 'ui-ux': '7057FF', + integration: '0E8A16', + performance: 'EDEDED', + 'cross-browser': '5319E7', + plus: 'FFD700', + beta: '0052CC', + alpha: '5319E7', + high: 'D93F0B', + medium: 'FBCA04', + low: '0E8A16', + critical: 'B60205' + }; + await github.rest.issues.createLabel({ + owner: context.repo.owner, + repo: context.repo.repo, + name: name, + color: colors[name.toLowerCase()] || '666666' + }); + console.log(`Created label: ${name}`); + } + } + } + + // Helper: Parse markdown file + function parseIssueFile(filePath) { + const content = fs.readFileSync(filePath, 'utf8'); + const { data: frontmatter, content: body } = matter(content); + + // Extract title: frontmatter > first H1 > filename + let title = frontmatter.title; + if (!title) { + const h1Match = body.match(/^#\s+(.+)$/m); + title = h1Match ? h1Match[1] : path.basename(filePath, '.md'); + } + + // Build labels array + const labels = [...(frontmatter.labels || [])]; + if (frontmatter.priority) labels.push(frontmatter.priority); + if (frontmatter.type) labels.push(frontmatter.type); + + return { + title, + body: body.trim(), + labels: [...new Set(labels)], + assignees: frontmatter.assignees || [], + milestone: frontmatter.milestone, + parent_issue: frontmatter.parent_issue, + create_sub_issues: frontmatter.create_sub_issues || false + }; + } + + // Helper: Extract sub-issues from H2 sections + function extractSubIssues(body, parentLabels) { + const sections = []; + const regex = /^##\s+(?:Sub-Issue\s*#?\d*:?\s*)?(.+?)$([\s\S]*?)(?=^##\s|\Z)/gm; + let match; + + while ((match = regex.exec(body)) !== null) { + const sectionTitle = match[1].trim(); + const sectionBody = match[2].trim(); + + // Extract inline labels like **Labels**: `testing`, `acl` + const inlineLabels = []; + const labelMatch = sectionBody.match(/\*\*Labels?\*\*:?\s*[`']?([^`'\n]+)[`']?/i); + if (labelMatch) { + labelMatch[1].split(',').forEach(l => { + const label = l.replace(/[`']/g, '').trim(); + if (label) inlineLabels.push(label); + }); + } + + sections.push({ + title: sectionTitle, + body: sectionBody, + labels: [...parentLabels, ...inlineLabels] + }); + } + + return sections; + } + + // Process each file + for (const filePath of files) { + console.log(`\nProcessing: ${filePath}`); + + try { + const parsed = parseIssueFile(filePath); + console.log(` Title: ${parsed.title}`); + console.log(` Labels: ${parsed.labels.join(', ')}`); + + if (isDryRun) { + console.log(' [DRY RUN] Would create issue'); + createdIssues.push({ file: filePath, title: parsed.title, dryRun: true }); + continue; + } + + // Ensure labels exist + for (const label of parsed.labels) { + await ensureLabel(label); + } + + // Create the main issue + const issueResponse = await github.rest.issues.create({ + owner: context.repo.owner, + repo: context.repo.repo, + title: parsed.title, + body: parsed.body + `\n\n---\n*Auto-created from [${path.basename(filePath)}](https://github.com/${context.repo.owner}/${context.repo.repo}/blob/${context.sha}/${filePath})*`, + labels: parsed.labels, + assignees: parsed.assignees + }); + + const issueNumber = issueResponse.data.number; + console.log(` Created issue #${issueNumber}`); + + // Handle sub-issues + if (parsed.create_sub_issues) { + const subIssues = extractSubIssues(parsed.body, parsed.labels); + for (const sub of subIssues) { + for (const label of sub.labels) { + await ensureLabel(label); + } + const subResponse = await github.rest.issues.create({ + owner: context.repo.owner, + repo: context.repo.repo, + title: `[${parsed.title}] ${sub.title}`, + body: sub.body + `\n\n---\n*Sub-issue of #${issueNumber}*`, + labels: sub.labels, + assignees: parsed.assignees + }); + console.log(` Created sub-issue #${subResponse.data.number}: ${sub.title}`); + } + } + + // Link to parent issue if specified + if (parsed.parent_issue) { + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: parsed.parent_issue, + body: `Sub-issue created: #${issueNumber}` + }); + } + + createdIssues.push({ + file: filePath, + title: parsed.title, + issueNumber + }); + + } catch (error) { + console.error(` Error processing ${filePath}: ${error.message}`); + errors.push({ file: filePath, error: error.message }); + } + } + + core.setOutput('created_count', createdIssues.length); + core.setOutput('created_issues', JSON.stringify(createdIssues)); + core.setOutput('errors', JSON.stringify(errors)); + + if (errors.length > 0) { + core.warning(`${errors.length} file(s) had errors`); + } + + - name: Move processed files + if: steps.process.outputs.created_count != '0' && github.event.inputs.dry_run != 'true' + run: | + mkdir -p docs/issues/created + CREATED_ISSUES='${{ steps.process.outputs.created_issues }}' + echo "$CREATED_ISSUES" | jq -r '.[].file' | while read file; do + if [ -f "$file" ] && [ ! -z "$file" ]; then + filename=$(basename "$file") + timestamp=$(date +%Y%m%d) + mv "$file" "docs/issues/created/${timestamp}-${filename}" + echo "Moved: $file -> docs/issues/created/${timestamp}-${filename}" + fi + done + + - name: Commit moved files + if: steps.process.outputs.created_count != '0' && github.event.inputs.dry_run != 'true' + run: | + git config --local user.email "github-actions[bot]@users.noreply.github.com" + git config --local user.name "github-actions[bot]" + git add docs/issues/ + git diff --staged --quiet || git commit -m "chore: move processed issue files to created/ [skip ci]" + git push + + - name: Add to project board + if: steps.process.outputs.created_count != '0' && github.event.inputs.dry_run != 'true' + continue-on-error: true + uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7 + env: + PROJECT_URL: ${{ secrets.PROJECT_URL }} + ADD_TO_PROJECT_PAT: ${{ secrets.ADD_TO_PROJECT_PAT }} + with: + github-token: ${{ secrets.ADD_TO_PROJECT_PAT || secrets.GITHUB_TOKEN }} + script: | + const projectUrl = process.env.PROJECT_URL; + if (!projectUrl) { + console.log('PROJECT_URL not configured, skipping project board'); + return; + } + + const createdIssues = JSON.parse('${{ steps.process.outputs.created_issues }}'); + console.log(`Would add ${createdIssues.length} issues to project board`); + // Note: Actual project board integration requires GraphQL API + // The actions/add-to-project action handles this automatically + // for issues created via normal flow + + - name: Summary + if: always() + run: | + echo "## Docs to Issues Summary" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + + CREATED='${{ steps.process.outputs.created_issues }}' + ERRORS='${{ steps.process.outputs.errors }}' + DRY_RUN='${{ github.event.inputs.dry_run }}' + + if [ "$DRY_RUN" = "true" ]; then + echo "🔍 **Dry Run Mode** - No issues were actually created" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + fi + + echo "### Created Issues" >> $GITHUB_STEP_SUMMARY + if [ -n "$CREATED" ] && [ "$CREATED" != "[]" ]; then + echo "$CREATED" | jq -r '.[] | "- \(.title) (#\(.issueNumber // "dry-run"))"' >> $GITHUB_STEP_SUMMARY + else + echo "_No issues created_" >> $GITHUB_STEP_SUMMARY + fi + + echo "" >> $GITHUB_STEP_SUMMARY + echo "### Errors" >> $GITHUB_STEP_SUMMARY + if [ -n "$ERRORS" ] && [ "$ERRORS" != "[]" ]; then + echo "$ERRORS" | jq -r '.[] | "- ❌ \(.file): \(.error)"' >> $GITHUB_STEP_SUMMARY + else + echo "_No errors_" >> $GITHUB_STEP_SUMMARY + fi +``` + +--- + +## 4. File Conversion Templates + +### 4.1 Updated ACL-testing-tasks.md (Example) + +```yaml +--- +title: "ACL: Test and validate ACL changes (feature/beta-release)" +labels: + - testing + - needs-triage + - acl + - regression +priority: high +milestone: "v0.2.0-beta.2" +create_sub_issues: false +--- + +# ACL: Test and validate ACL changes (feature/beta-release) + +**Repository:** Wikid82/Charon +**Branch:** feature/beta-release + +## Purpose + +Create a tracked issue and sub-tasks to validate ACL-related changes... + +[rest of content unchanged] +``` + +### 4.2 Updated bulk-acl-subissues.md (Example with Sub-Issues) + +```yaml +--- +title: "Bulk ACL Testing - Sub-Issues" +labels: + - testing + - manual-testing + - bulk-acl +priority: medium +milestone: "v0.2.0-beta.2" +create_sub_issues: true +--- + +# Bulk ACL Testing - Sub-Issues + +## Basic Functionality Testing + +**Labels**: `testing`, `manual-testing`, `bulk-acl` + +Test the core functionality of the bulk ACL feature... + +## ACL Removal Testing + +**Labels**: `testing`, `manual-testing`, `bulk-acl` + +Test the ability to remove access lists... + +[each ## section becomes a sub-issue] +``` + +### 4.3 Template for New Issue Files + +Create `docs/issues/_TEMPLATE.md`: + +```yaml +--- +# REQUIRED: Issue title +title: "Your Issue Title" + +# OPTIONAL: Labels to apply (will be created if missing) +labels: + - feature # feature, bug, enhancement, testing, documentation + - backend # backend, frontend, ui, security, caddy, database + +# OPTIONAL: Priority (creates matching label) +priority: medium # critical, high, medium, low + +# OPTIONAL: Milestone name +milestone: "v0.2.0-beta.2" + +# OPTIONAL: GitHub usernames to assign +assignees: [] + +# OPTIONAL: Parent issue number for linking +# parent_issue: 42 + +# OPTIONAL: Parse ## sections as separate sub-issues +# create_sub_issues: true +--- + +# Issue Title + +## Description + +Clear description of the issue or feature request. + +## Tasks + +- [ ] Task 1 +- [ ] Task 2 +- [ ] Task 3 + +## Acceptance Criteria + +- [ ] Criterion 1 +- [ ] Criterion 2 + +## Related Issues + +- #XX - Related issue description + +--- + +*Issue specification created: YYYY-MM-DD* +``` + +--- + +## 5. Files to Update + +### 5.1 Existing docs/issues Files Needing Frontmatter + +| File | Action Needed | +|------|---------------| +| `ACL-testing-tasks.md` | Add frontmatter with extracted metadata | +| `Additional_Security.md` | Add frontmatter, convert to issue format | +| `bulk-acl-subissues.md` | Add frontmatter with `create_sub_issues: true` | +| `bulk-acl-testing.md` | Add frontmatter with extracted metadata | +| `hectate.md` | Add frontmatter (feature spec) | +| `orthrus.md` | Add frontmatter (feature spec) | +| `plex-remote-access-helper.md` | Add frontmatter, already has metadata section | +| `rotating-loading-animations.md` | Add frontmatter, extract inline metadata | + +### 5.2 New Files to Create + +| File | Purpose | +|------|---------| +| `.github/workflows/docs-to-issues.yml` | Main workflow | +| `docs/issues/_TEMPLATE.md` | Template for new issues | +| `docs/issues/created/.gitkeep` | Directory for processed files | +| `docs/issues/README.md` | Documentation for contributors | + +--- + +## 6. Directory Structure + +``` +docs/ +├── issues/ +│ ├── README.md # How to create issue files +│ ├── _TEMPLATE.md # Template for new issues +│ ├── created/ # Processed files moved here +│ │ └── .gitkeep +│ ├── ACL-testing-tasks.md # Pending (needs frontmatter) +│ ├── Additional_Security.md # Pending +│ ├── bulk-acl-subissues.md # Pending +│ └── ... +``` + +--- + +## 7. Duplicate Prevention Strategy + +### 7.1 File-Based Tracking + +- **Processed files move to `docs/issues/created/`** with timestamp prefix +- **Workflow excludes `created/` directory** from processing +- **Git history provides audit trail** of when files were converted + +### 7.2 Issue Tracking + +Each created issue includes footer: +```markdown +--- +*Auto-created from [filename.md](link-to-source-commit)* +``` + +### 7.3 Manual Override + +- Use `workflow_dispatch` with specific file path to reprocess +- Files in `created/` can be moved back for reprocessing + +--- + +## 8. Project Board Integration + +### 8.1 Automatic Addition + +The workflow leverages the existing `auto-add-to-project.yml` which triggers on `issues: [opened]`. + +When the workflow creates an issue, the auto-add workflow automatically adds it to the project board (if `PROJECT_URL` secret is configured). + +### 8.2 Manual Configuration + +If not using `auto-add-to-project.yml`, configure in the docs-to-issues workflow: + +```yaml +- name: Add to project + uses: actions/add-to-project@244f685bbc3b7adfa8466e08b698b5577571133e # v1.0.2 + with: + project-url: ${{ secrets.PROJECT_URL }} + github-token: ${{ secrets.ADD_TO_PROJECT_PAT }} +``` + +--- + +## 9. Testing Strategy + +### 9.1 Dry Run Testing + +```bash +# Manually trigger with dry_run=true +gh workflow run docs-to-issues.yml -f dry_run=true + +# Test specific file +gh workflow run docs-to-issues.yml -f dry_run=true -f file_path=docs/issues/ACL-testing-tasks.md +``` + +### 9.2 Local Testing + +```bash +# Parse frontmatter locally +node -e " +const matter = require('gray-matter'); +const fs = require('fs'); +const result = matter(fs.readFileSync('docs/issues/ACL-testing-tasks.md', 'utf8')); +console.log(JSON.stringify(result.data, null, 2)); +" +``` + +### 9.3 Validation Checklist + +- [ ] Frontmatter parses correctly for all files +- [ ] Labels are created when missing +- [ ] Issue titles are correct +- [ ] Issue bodies include full content +- [ ] Sub-issues are created correctly (if `create_sub_issues: true`) +- [ ] Files move to `created/` after processing +- [ ] Auto-add-to-project triggers for new issues +- [ ] Commit message includes `[skip ci]` to prevent loops + +--- + +## 10. Implementation Phases + +### Phase 1: Setup (15 min) +1. Create `.github/workflows/docs-to-issues.yml` +2. Create `docs/issues/created/.gitkeep` +3. Create `docs/issues/_TEMPLATE.md` +4. Create `docs/issues/README.md` + +### Phase 2: File Migration (30 min) +1. Add frontmatter to existing files (in order of priority) +2. Test with dry_run mode +3. Create one test issue to verify + +### Phase 3: Validation (15 min) +1. Verify issue creation +2. Verify label creation +3. Verify project board integration +4. Verify file move to `created/` + +--- + +## 11. Risk Assessment + +| Risk | Impact | Mitigation | +|------|--------|------------| +| Duplicate issues | Low | File move + exclude pattern | +| Label spam | Low | Predefined color map | +| Rate limiting | Medium | Process files sequentially | +| Malformed frontmatter | Medium | Try-catch with error logging | +| Project board auth failure | Low | `continue-on-error: true` | + +--- + +## 12. Definition of Done + +- [ ] Workflow file created and committed +- [ ] All existing docs/issues files have valid frontmatter +- [ ] Dry run succeeds with no errors +- [ ] At least one test issue created successfully +- [ ] File moved to `created/` after processing +- [ ] Labels created automatically +- [ ] Project board integration verified (if configured) +- [ ] Documentation in `docs/issues/README.md` + +--- + +## Appendix A: Label Color Reference + +```javascript +const labelColors = { + // Priority + critical: 'B60205', + high: 'D93F0B', + medium: 'FBCA04', + low: '0E8A16', + + // Milestone + alpha: '5319E7', + beta: '0052CC', + 'post-beta': '006B75', + + // Type + feature: 'A2EEEF', + bug: 'D73A4A', + enhancement: '84B6EB', + documentation: '0075CA', + testing: 'BFD4F2', + + // Component + backend: '1D76DB', + frontend: '5EBEFF', + ui: '7057FF', + security: 'EE0701', + caddy: '1F6FEB', + database: '006B75', + + // Custom + 'needs-triage': 'FBCA04', + acl: 'C5DEF5', + 'bulk-acl': '006B75', + 'manual-testing': 'BFD4F2', + regression: 'D93F0B', + plus: 'FFD700' +}; +``` + +--- + +## Appendix B: Frontmatter Conversion Examples + +### B.1 hectate.md (Feature Spec) + +```yaml +--- +title: "Hecate: Tunnel & Pathway Manager" +labels: + - feature + - backend + - architecture +priority: medium +milestone: "post-beta" +type: feature +--- +``` + +### B.2 orthrus.md (Feature Spec) + +```yaml +--- +title: "Orthrus: Remote Socket Proxy Agent" +labels: + - feature + - backend + - architecture +priority: medium +milestone: "post-beta" +type: feature +--- +``` + +### B.3 plex-remote-access-helper.md + +```yaml +--- +title: "Plex Remote Access Helper & CGNAT Solver" +labels: + - beta + - feature + - plus + - ui + - caddy +priority: medium +milestone: "beta" +type: feature +parent_issue: 44 +--- +``` + +### B.4 rotating-loading-animations.md + +```yaml +--- +title: "Enhancement: Rotating Thematic Loading Animations" +labels: + - enhancement + - frontend + - ui +priority: low +type: enhancement +--- +``` + +### B.5 Additional_Security.md + +```yaml +--- +title: "Additional Security Threats to Consider" +labels: + - security + - documentation + - architecture +priority: medium +type: documentation +--- +``` diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 6e44bac1..87c51ac1 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -12,9 +12,9 @@ "axios": "^1.13.2", "clsx": "^2.1.1", "date-fns": "^4.1.0", - "lucide-react": "^0.556.0", - "react": "^19.2.1", - "react-dom": "^19.2.1", + "lucide-react": "^0.561.0", + "react": "^19.2.3", + "react-dom": "^19.2.3", "react-hook-form": "^7.68.0", "react-hot-toast": "^2.6.0", "react-router-dom": "^7.10.1", @@ -23,7 +23,7 @@ }, "devDependencies": { "@playwright/test": "^1.57.0", - "@tailwindcss/postcss": "^4.1.17", + "@tailwindcss/postcss": "^4.1.18", "@testing-library/jest-dom": "^6.9.1", "@testing-library/react": "^16.3.0", "@testing-library/user-event": "^14.6.1", @@ -36,13 +36,13 @@ "@vitest/coverage-v8": "^4.0.15", "@vitest/ui": "^4.0.15", "autoprefixer": "^10.4.22", - "eslint": "^9.39.1", + "eslint": "^9.39.2", "eslint-plugin-react-hooks": "^7.0.1", "eslint-plugin-react-refresh": "^0.4.24", "jsdom": "^27.3.0", - "knip": "^5.72.0", + "knip": "^5.73.4", "postcss": "^8.5.6", - "tailwindcss": "^4.1.17", + "tailwindcss": "^4.1.18", "typescript": "^5.9.3", "typescript-eslint": "^8.49.0", "vite": "^7.2.7", @@ -1186,10 +1186,11 @@ } }, "node_modules/@eslint/js": { - "version": "9.39.1", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.1.tgz", - "integrity": "sha512-S26Stp4zCy88tH94QbBv3XCuzRQiZ9yXofEILmglYTh/Ug/a9/umqvgFtYBAo3Lp0nsI/5/qH1CCrbdK3AP1Tw==", + "version": "9.39.2", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.2.tgz", + "integrity": "sha512-q1mjIoW1VX4IvSocvM/vbTiveKC4k9eLrajNEuSsmjymSDEbpGddtpfOoN7YGAqBK3NG+uqo8ia4PDTt8buCYA==", "dev": true, + "license": "MIT", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, @@ -1998,10 +1999,11 @@ "dev": true }, "node_modules/@tailwindcss/node": { - "version": "4.1.17", - "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.1.17.tgz", - "integrity": "sha512-csIkHIgLb3JisEFQ0vxr2Y57GUNYh447C8xzwj89U/8fdW8LhProdxvnVH6U8M2Y73QKiTIH+LWbK3V2BBZsAg==", + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.1.18.tgz", + "integrity": "sha512-DoR7U1P7iYhw16qJ49fgXUlry1t4CpXeErJHnQ44JgTSKMaZUdf17cfn5mHchfJ4KRBZRFA/Coo+MUF5+gOaCQ==", "dev": true, + "license": "MIT", "dependencies": { "@jridgewell/remapping": "^2.3.4", "enhanced-resolve": "^5.18.3", @@ -2009,40 +2011,42 @@ "lightningcss": "1.30.2", "magic-string": "^0.30.21", "source-map-js": "^1.2.1", - "tailwindcss": "4.1.17" + "tailwindcss": "4.1.18" } }, "node_modules/@tailwindcss/oxide": { - "version": "4.1.17", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.1.17.tgz", - "integrity": "sha512-F0F7d01fmkQhsTjXezGBLdrl1KresJTcI3DB8EkScCldyKp3Msz4hub4uyYaVnk88BAS1g5DQjjF6F5qczheLA==", + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.1.18.tgz", + "integrity": "sha512-EgCR5tTS5bUSKQgzeMClT6iCY3ToqE1y+ZB0AKldj809QXk1Y+3jB0upOYZrn9aGIzPtUsP7sX4QQ4XtjBB95A==", "dev": true, + "license": "MIT", "engines": { "node": ">= 10" }, "optionalDependencies": { - "@tailwindcss/oxide-android-arm64": "4.1.17", - "@tailwindcss/oxide-darwin-arm64": "4.1.17", - "@tailwindcss/oxide-darwin-x64": "4.1.17", - "@tailwindcss/oxide-freebsd-x64": "4.1.17", - "@tailwindcss/oxide-linux-arm-gnueabihf": "4.1.17", - "@tailwindcss/oxide-linux-arm64-gnu": "4.1.17", - "@tailwindcss/oxide-linux-arm64-musl": "4.1.17", - "@tailwindcss/oxide-linux-x64-gnu": "4.1.17", - "@tailwindcss/oxide-linux-x64-musl": "4.1.17", - "@tailwindcss/oxide-wasm32-wasi": "4.1.17", - "@tailwindcss/oxide-win32-arm64-msvc": "4.1.17", - "@tailwindcss/oxide-win32-x64-msvc": "4.1.17" + "@tailwindcss/oxide-android-arm64": "4.1.18", + "@tailwindcss/oxide-darwin-arm64": "4.1.18", + "@tailwindcss/oxide-darwin-x64": "4.1.18", + "@tailwindcss/oxide-freebsd-x64": "4.1.18", + "@tailwindcss/oxide-linux-arm-gnueabihf": "4.1.18", + "@tailwindcss/oxide-linux-arm64-gnu": "4.1.18", + "@tailwindcss/oxide-linux-arm64-musl": "4.1.18", + "@tailwindcss/oxide-linux-x64-gnu": "4.1.18", + "@tailwindcss/oxide-linux-x64-musl": "4.1.18", + "@tailwindcss/oxide-wasm32-wasi": "4.1.18", + "@tailwindcss/oxide-win32-arm64-msvc": "4.1.18", + "@tailwindcss/oxide-win32-x64-msvc": "4.1.18" } }, "node_modules/@tailwindcss/oxide-android-arm64": { - "version": "4.1.17", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.1.17.tgz", - "integrity": "sha512-BMqpkJHgOZ5z78qqiGE6ZIRExyaHyuxjgrJ6eBO5+hfrfGkuya0lYfw8fRHG77gdTjWkNWEEm+qeG2cDMxArLQ==", + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.1.18.tgz", + "integrity": "sha512-dJHz7+Ugr9U/diKJA0W6N/6/cjI+ZTAoxPf9Iz9BFRF2GzEX8IvXxFIi/dZBloVJX/MZGvRuFA9rqwdiIEZQ0Q==", "cpu": [ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "android" @@ -2052,13 +2056,14 @@ } }, "node_modules/@tailwindcss/oxide-darwin-arm64": { - "version": "4.1.17", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.1.17.tgz", - "integrity": "sha512-EquyumkQweUBNk1zGEU/wfZo2qkp/nQKRZM8bUYO0J+Lums5+wl2CcG1f9BgAjn/u9pJzdYddHWBiFXJTcxmOg==", + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.1.18.tgz", + "integrity": "sha512-Gc2q4Qhs660bhjyBSKgq6BYvwDz4G+BuyJ5H1xfhmDR3D8HnHCmT/BSkvSL0vQLy/nkMLY20PQ2OoYMO15Jd0A==", "cpu": [ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "darwin" @@ -2068,13 +2073,14 @@ } }, "node_modules/@tailwindcss/oxide-darwin-x64": { - "version": "4.1.17", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.1.17.tgz", - "integrity": "sha512-gdhEPLzke2Pog8s12oADwYu0IAw04Y2tlmgVzIN0+046ytcgx8uZmCzEg4VcQh+AHKiS7xaL8kGo/QTiNEGRog==", + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.1.18.tgz", + "integrity": "sha512-FL5oxr2xQsFrc3X9o1fjHKBYBMD1QZNyc1Xzw/h5Qu4XnEBi3dZn96HcHm41c/euGV+GRiXFfh2hUCyKi/e+yw==", "cpu": [ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "darwin" @@ -2084,13 +2090,14 @@ } }, "node_modules/@tailwindcss/oxide-freebsd-x64": { - "version": "4.1.17", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.1.17.tgz", - "integrity": "sha512-hxGS81KskMxML9DXsaXT1H0DyA+ZBIbyG/sSAjWNe2EDl7TkPOBI42GBV3u38itzGUOmFfCzk1iAjDXds8Oh0g==", + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.1.18.tgz", + "integrity": "sha512-Fj+RHgu5bDodmV1dM9yAxlfJwkkWvLiRjbhuO2LEtwtlYlBgiAT4x/j5wQr1tC3SANAgD+0YcmWVrj8R9trVMA==", "cpu": [ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "freebsd" @@ -2100,13 +2107,14 @@ } }, "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": { - "version": "4.1.17", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.1.17.tgz", - "integrity": "sha512-k7jWk5E3ldAdw0cNglhjSgv501u7yrMf8oeZ0cElhxU6Y2o7f8yqelOp3fhf7evjIS6ujTI3U8pKUXV2I4iXHQ==", + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.1.18.tgz", + "integrity": "sha512-Fp+Wzk/Ws4dZn+LV2Nqx3IilnhH51YZoRaYHQsVq3RQvEl+71VGKFpkfHrLM/Li+kt5c0DJe/bHXK1eHgDmdiA==", "cpu": [ "arm" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" @@ -2116,13 +2124,14 @@ } }, "node_modules/@tailwindcss/oxide-linux-arm64-gnu": { - "version": "4.1.17", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.1.17.tgz", - "integrity": "sha512-HVDOm/mxK6+TbARwdW17WrgDYEGzmoYayrCgmLEw7FxTPLcp/glBisuyWkFz/jb7ZfiAXAXUACfyItn+nTgsdQ==", + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.1.18.tgz", + "integrity": "sha512-S0n3jboLysNbh55Vrt7pk9wgpyTTPD0fdQeh7wQfMqLPM/Hrxi+dVsLsPrycQjGKEQk85Kgbx+6+QnYNiHalnw==", "cpu": [ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" @@ -2132,13 +2141,14 @@ } }, "node_modules/@tailwindcss/oxide-linux-arm64-musl": { - "version": "4.1.17", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.1.17.tgz", - "integrity": "sha512-HvZLfGr42i5anKtIeQzxdkw/wPqIbpeZqe7vd3V9vI3RQxe3xU1fLjss0TjyhxWcBaipk7NYwSrwTwK1hJARMg==", + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.1.18.tgz", + "integrity": "sha512-1px92582HkPQlaaCkdRcio71p8bc8i/ap5807tPRDK/uw953cauQBT8c5tVGkOwrHMfc2Yh6UuxaH4vtTjGvHg==", "cpu": [ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" @@ -2148,13 +2158,14 @@ } }, "node_modules/@tailwindcss/oxide-linux-x64-gnu": { - "version": "4.1.17", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.1.17.tgz", - "integrity": "sha512-M3XZuORCGB7VPOEDH+nzpJ21XPvK5PyjlkSFkFziNHGLc5d6g3di2McAAblmaSUNl8IOmzYwLx9NsE7bplNkwQ==", + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.1.18.tgz", + "integrity": "sha512-v3gyT0ivkfBLoZGF9LyHmts0Isc8jHZyVcbzio6Wpzifg/+5ZJpDiRiUhDLkcr7f/r38SWNe7ucxmGW3j3Kb/g==", "cpu": [ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" @@ -2164,13 +2175,14 @@ } }, "node_modules/@tailwindcss/oxide-linux-x64-musl": { - "version": "4.1.17", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.1.17.tgz", - "integrity": "sha512-k7f+pf9eXLEey4pBlw+8dgfJHY4PZ5qOUFDyNf7SI6lHjQ9Zt7+NcscjpwdCEbYi6FI5c2KDTDWyf2iHcCSyyQ==", + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.1.18.tgz", + "integrity": "sha512-bhJ2y2OQNlcRwwgOAGMY0xTFStt4/wyU6pvI6LSuZpRgKQwxTec0/3Scu91O8ir7qCR3AuepQKLU/kX99FouqQ==", "cpu": [ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" @@ -2180,9 +2192,9 @@ } }, "node_modules/@tailwindcss/oxide-wasm32-wasi": { - "version": "4.1.17", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.1.17.tgz", - "integrity": "sha512-cEytGqSSoy7zK4JRWiTCx43FsKP/zGr0CsuMawhH67ONlH+T79VteQeJQRO/X7L0juEUA8ZyuYikcRBf0vsxhg==", + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.1.18.tgz", + "integrity": "sha512-LffYTvPjODiP6PT16oNeUQJzNVyJl1cjIebq/rWWBF+3eDst5JGEFSc5cWxyRCJ0Mxl+KyIkqRxk1XPEs9x8TA==", "bundleDependencies": [ "@napi-rs/wasm-runtime", "@emnapi/core", @@ -2195,12 +2207,13 @@ "wasm32" ], "dev": true, + "license": "MIT", "optional": true, "dependencies": { - "@emnapi/core": "^1.6.0", - "@emnapi/runtime": "^1.6.0", + "@emnapi/core": "^1.7.1", + "@emnapi/runtime": "^1.7.1", "@emnapi/wasi-threads": "^1.1.0", - "@napi-rs/wasm-runtime": "^1.0.7", + "@napi-rs/wasm-runtime": "^1.1.0", "@tybys/wasm-util": "^0.10.1", "tslib": "^2.4.0" }, @@ -2209,7 +2222,7 @@ } }, "node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/core": { - "version": "1.6.0", + "version": "1.7.1", "dev": true, "inBundle": true, "license": "MIT", @@ -2220,7 +2233,7 @@ } }, "node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/runtime": { - "version": "1.6.0", + "version": "1.7.1", "dev": true, "inBundle": true, "license": "MIT", @@ -2240,14 +2253,14 @@ } }, "node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@napi-rs/wasm-runtime": { - "version": "1.0.7", + "version": "1.1.0", "dev": true, "inBundle": true, "license": "MIT", "optional": true, "dependencies": { - "@emnapi/core": "^1.5.0", - "@emnapi/runtime": "^1.5.0", + "@emnapi/core": "^1.7.1", + "@emnapi/runtime": "^1.7.1", "@tybys/wasm-util": "^0.10.1" } }, @@ -2269,13 +2282,14 @@ "optional": true }, "node_modules/@tailwindcss/oxide-win32-arm64-msvc": { - "version": "4.1.17", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.1.17.tgz", - "integrity": "sha512-JU5AHr7gKbZlOGvMdb4722/0aYbU+tN6lv1kONx0JK2cGsh7g148zVWLM0IKR3NeKLv+L90chBVYcJ8uJWbC9A==", + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.1.18.tgz", + "integrity": "sha512-HjSA7mr9HmC8fu6bdsZvZ+dhjyGCLdotjVOgLA2vEqxEBZaQo9YTX4kwgEvPCpRh8o4uWc4J/wEoFzhEmjvPbA==", "cpu": [ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "win32" @@ -2285,13 +2299,14 @@ } }, "node_modules/@tailwindcss/oxide-win32-x64-msvc": { - "version": "4.1.17", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.1.17.tgz", - "integrity": "sha512-SKWM4waLuqx0IH+FMDUw6R66Hu4OuTALFgnleKbqhgGU30DY20NORZMZUKgLRjQXNN2TLzKvh48QXTig4h4bGw==", + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.1.18.tgz", + "integrity": "sha512-bJWbyYpUlqamC8dpR7pfjA0I7vdF6t5VpUGMWRkXVE3AXgIZjYUYAK7II1GNaxR8J1SSrSrppRar8G++JekE3Q==", "cpu": [ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "win32" @@ -2301,16 +2316,17 @@ } }, "node_modules/@tailwindcss/postcss": { - "version": "4.1.17", - "resolved": "https://registry.npmjs.org/@tailwindcss/postcss/-/postcss-4.1.17.tgz", - "integrity": "sha512-+nKl9N9mN5uJ+M7dBOOCzINw94MPstNR/GtIhz1fpZysxL/4a+No64jCBD6CPN+bIHWFx3KWuu8XJRrj/572Dw==", + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/postcss/-/postcss-4.1.18.tgz", + "integrity": "sha512-Ce0GFnzAOuPyfV5SxjXGn0CubwGcuDB0zcdaPuCSzAa/2vII24JTkH+I6jcbXLb1ctjZMZZI6OjDaLPJQL1S0g==", "dev": true, + "license": "MIT", "dependencies": { "@alloc/quick-lru": "^5.2.0", - "@tailwindcss/node": "4.1.17", - "@tailwindcss/oxide": "4.1.17", + "@tailwindcss/node": "4.1.18", + "@tailwindcss/oxide": "4.1.18", "postcss": "^8.4.41", - "tailwindcss": "4.1.17" + "tailwindcss": "4.1.18" } }, "node_modules/@tanstack/query-core": { @@ -3644,9 +3660,9 @@ } }, "node_modules/eslint": { - "version": "9.39.1", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.1.tgz", - "integrity": "sha512-BhHmn2yNOFA9H9JmmIVKJmd288g9hrVRDkdoIgRCRuSySRUHH7r/DI6aAXW9T1WwUuY3DFgrcaqB+deURBLR5g==", + "version": "9.39.2", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.2.tgz", + "integrity": "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==", "dev": true, "license": "MIT", "peer": true, @@ -3657,7 +3673,7 @@ "@eslint/config-helpers": "^0.4.2", "@eslint/core": "^0.17.0", "@eslint/eslintrc": "^3.3.1", - "@eslint/js": "9.39.1", + "@eslint/js": "9.39.2", "@eslint/plugin-kit": "^0.4.1", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", @@ -4620,9 +4636,9 @@ } }, "node_modules/knip": { - "version": "5.72.0", - "resolved": "https://registry.npmjs.org/knip/-/knip-5.72.0.tgz", - "integrity": "sha512-rlyoXI8FcggNtM/QXd/GW0sbsYvNuA/zPXt7bsuVi6kVQogY2PDCr81bPpzNnl0CP8AkFm2Z2plVeL5QQSis2w==", + "version": "5.73.4", + "resolved": "https://registry.npmjs.org/knip/-/knip-5.73.4.tgz", + "integrity": "sha512-q0DDgqsRMa4z2IMEPEblns0igitG8Fu7exkvEgQx1QMLKEqSvcvKP9fMk+C1Ehy+Ux6oayl6zfAEGt6DvFtidw==", "dev": true, "funding": [ { @@ -4967,9 +4983,9 @@ } }, "node_modules/lucide-react": { - "version": "0.556.0", - "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.556.0.tgz", - "integrity": "sha512-iOb8dRk7kLaYBZhR2VlV1CeJGxChBgUthpSP8wom9jfj79qovgG6qcSdiy6vkoREKPnbUYzJsCn4o4PtG3Iy+A==", + "version": "0.561.0", + "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.561.0.tgz", + "integrity": "sha512-Y59gMY38tl4/i0qewcqohPdEbieBy7SovpBL9IFebhc2mDd8x4PZSOsiFRkpPcOq6bj1r/mjH/Rk73gSlIJP2A==", "license": "ISC", "peerDependencies": { "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" @@ -5498,9 +5514,9 @@ "license": "MIT" }, "node_modules/react": { - "version": "19.2.1", - "resolved": "https://registry.npmjs.org/react/-/react-19.2.1.tgz", - "integrity": "sha512-DGrYcCWK7tvYMnWh79yrPHt+vdx9tY+1gPZa7nJQtO/p8bLTDaHp4dzwEhQB7pZ4Xe3ok4XKuEPrVuc+wlpkmw==", + "version": "19.2.3", + "resolved": "https://registry.npmjs.org/react/-/react-19.2.3.tgz", + "integrity": "sha512-Ku/hhYbVjOQnXDZFv2+RibmLFGwFdeeKHFcOTlrt7xplBnya5OGn/hIRDsqDiSUcfORsDC7MPxwork8jBwsIWA==", "license": "MIT", "peer": true, "engines": { @@ -5508,16 +5524,16 @@ } }, "node_modules/react-dom": { - "version": "19.2.1", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.1.tgz", - "integrity": "sha512-ibrK8llX2a4eOskq1mXKu/TGZj9qzomO+sNfO98M6d9zIPOEhlBkMkBUBLd1vgS0gQsLDBzA+8jJBVXDnfHmJg==", + "version": "19.2.3", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.3.tgz", + "integrity": "sha512-yELu4WmLPw5Mr/lmeEpox5rw3RETacE++JgHqQzd2dg+YbJuat3jH4ingc+WPZhxaoFzdv9y33G+F7Nl5O0GBg==", "license": "MIT", "peer": true, "dependencies": { "scheduler": "^0.27.0" }, "peerDependencies": { - "react": "^19.2.1" + "react": "^19.2.3" } }, "node_modules/react-hook-form": { @@ -5883,9 +5899,9 @@ } }, "node_modules/tailwindcss": { - "version": "4.1.17", - "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.17.tgz", - "integrity": "sha512-j9Ee2YjuQqYT9bbRTfTZht9W/ytp5H+jJpZKiYdP/bpnXARAuELt9ofP0lPnmHjbga7SNQIxdTAXCmtKVYjN+Q==", + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.18.tgz", + "integrity": "sha512-4+Z+0yiYyEtUVCScyfHCxOYP06L5Ne+JiHhY2IjR2KWMIWhJOYZKLSGZaP5HkZ8+bY0cxfzwDE5uOmzFXyIwxw==", "dev": true, "license": "MIT" }, diff --git a/frontend/package.json b/frontend/package.json index 1af556c5..260b0f46 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -32,9 +32,9 @@ "axios": "^1.13.2", "clsx": "^2.1.1", "date-fns": "^4.1.0", - "lucide-react": "^0.556.0", - "react": "^19.2.1", - "react-dom": "^19.2.1", + "lucide-react": "^0.561.0", + "react": "^19.2.3", + "react-dom": "^19.2.3", "react-hook-form": "^7.68.0", "react-hot-toast": "^2.6.0", "react-router-dom": "^7.10.1", @@ -43,7 +43,7 @@ }, "devDependencies": { "@playwright/test": "^1.57.0", - "@tailwindcss/postcss": "^4.1.17", + "@tailwindcss/postcss": "^4.1.18", "@testing-library/jest-dom": "^6.9.1", "@testing-library/react": "^16.3.0", "@testing-library/user-event": "^14.6.1", @@ -57,13 +57,13 @@ "@vitest/ui": "^4.0.15", "autoprefixer": "^10.4.22", - "eslint": "^9.39.1", + "eslint": "^9.39.2", "eslint-plugin-react-hooks": "^7.0.1", "eslint-plugin-react-refresh": "^0.4.24", "jsdom": "^27.3.0", - "knip": "^5.72.0", + "knip": "^5.73.4", "postcss": "^8.5.6", - "tailwindcss": "^4.1.17", + "tailwindcss": "^4.1.18", "typescript": "^5.9.3", "typescript-eslint": "^8.49.0", "vite": "^7.2.7",