diff --git a/.env.example b/.env.example index 2fefd06..8a6cadf 100644 --- a/.env.example +++ b/.env.example @@ -6,6 +6,8 @@ MONGODB_URI=mongodb://localhost:27017/ghostguild # HELCIM_API_TOKEN=your-live-helcim-api-token HELCIM_API_TOKEN=your-test-helcim-api-token NUXT_PUBLIC_HELCIM_ACCOUNT_ID=your-helcim-account-id +NUXT_HELCIM_MONTHLY_PLAN_ID= +NUXT_HELCIM_ANNUAL_PLAN_ID= # Email Configuration (Resend) RESEND_API_KEY=your-resend-api-key diff --git a/.forgejo/workflows/test.yml b/.forgejo/workflows/test.yml index 8edaae1..2a29c40 100644 --- a/.forgejo/workflows/test.yml +++ b/.forgejo/workflows/test.yml @@ -21,16 +21,16 @@ jobs: playwright: runs-on: ubuntu-latest needs: vitest - services: - mongo: - image: mongo:7 - ports: - - 27017:27017 env: - MONGODB_URI: mongodb://localhost:27017/ghostguild-test + MONGODB_URI: mongodb://mongo-ci:27017/ghostguild-test JWT_SECRET: ci-test-jwt-secret + RESEND_API_KEY: re_ci_dummy_not_used + HELCIM_API_TOKEN: helcim_ci_dummy_not_used + OIDC_COOKIE_SECRET: ci-oidc-cookie-secret-not-secret NUXT_PUBLIC_COMING_SOON: 'false' NODE_ENV: development + ALLOW_DEV_TEST_ENDPOINTS: 'true' + BASE_URL: http://localhost:3000 steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 @@ -39,15 +39,35 @@ jobs: cache: npm - run: npm ci - run: npx playwright install --with-deps chromium + - name: Start MongoDB + run: | + docker rm -f mongo-ci 2>/dev/null || true + docker run -d --name mongo-ci mongo:7 + # Forgejo runs each job inside its own container; attach Mongo to + # that container's network so MONGODB_URI=mongodb://mongo-ci:27017 + # resolves from inside the runner. + RUNNER_NET=$(docker inspect "$HOSTNAME" --format '{{range $k,$v := .NetworkSettings.Networks}}{{$k}} {{end}}' | awk '{print $1}') + docker network connect "$RUNNER_NET" mongo-ci + docker ps + - name: Wait for MongoDB + run: timeout 30 sh -c 'until docker exec mongo-ci mongosh --quiet --eval "1" >/dev/null 2>&1; do sleep 1; done' + - name: MongoDB log on failure + if: failure() + run: docker logs mongo-ci || true + - name: Seed test data + run: node scripts/seed-all.js && node scripts/seed-tags.js - run: npm run build - name: Start server - run: node .output/server/index.mjs & + run: node .output/server/index.mjs > /tmp/server.log 2>&1 & env: PORT: 3000 - name: Wait for server run: timeout 30 sh -c 'until curl -sf http://localhost:3000; do sleep 1; done' - - run: npx playwright test --ignore-snapshots - - uses: actions/upload-artifact@v4 + - name: Server log on failure + if: failure() + run: cat /tmp/server.log || true + - run: npx playwright test + - uses: actions/upload-artifact@v3 if: failure() with: name: playwright-report @@ -68,39 +88,3 @@ jobs: -H 'Content-type: application/json' \ --data "{\"text\":\":x: *Ghost Guild CI failed* on \`${{ github.ref_name }}\`\nCommit: ${{ github.sha }}\n${{ github.server_url }}/${{ github.repository }}/actions\"}" - visual: - runs-on: ubuntu-latest - needs: vitest - continue-on-error: true - services: - mongo: - image: mongo:7 - ports: - - 27017:27017 - env: - MONGODB_URI: mongodb://localhost:27017/ghostguild-test - JWT_SECRET: ci-test-jwt-secret - NUXT_PUBLIC_COMING_SOON: 'false' - NODE_ENV: development - steps: - - uses: actions/checkout@v4 - - uses: actions/setup-node@v4 - with: - node-version: 22 - cache: npm - - run: npm ci - - run: npx playwright install --with-deps chromium - - run: npm run build - - name: Start server - run: node .output/server/index.mjs & - env: - PORT: 3000 - - name: Wait for server - run: timeout 30 sh -c 'until curl -sf http://localhost:3000; do sleep 1; done' - - run: npx playwright test e2e/visual/ - - uses: actions/upload-artifact@v4 - if: failure() - with: - name: visual-diffs - path: e2e/test-results/ - retention-days: 7 diff --git a/.gitignore b/.gitignore index 5ca2fab..3907ee0 100644 --- a/.gitignore +++ b/.gitignore @@ -26,6 +26,9 @@ logs !.env.example scripts/*.js +# Migration backup files +.migration-backup-*.json + # Playwright e2e/test-results/ playwright-report/ @@ -35,3 +38,6 @@ e2e/.auth/ .worktrees/ .claude/worktrees/ .superpowers/ + +.claude +scripts/dump-babyghosts-preregistrations.mjs diff --git a/.serena/project.yml b/.serena/project.yml index 9d24cb3..ddf640a 100644 --- a/.serena/project.yml +++ b/.serena/project.yml @@ -3,15 +3,18 @@ project_name: "ghostguild-org" # list of languages for which language servers are started; choose from: -# al bash clojure cpp csharp -# csharp_omnisharp dart elixir elm erlang -# fortran fsharp go groovy haskell -# java julia kotlin lua markdown -# matlab nix pascal perl php -# php_phpactor powershell python python_jedi r -# rego ruby ruby_solargraph rust scala -# swift terraform toml typescript typescript_vts -# vue yaml zig +# al ansible bash clojure cpp +# cpp_ccls crystal csharp csharp_omnisharp dart +# elixir elm erlang fortran fsharp +# go groovy haskell haxe hlsl +# java json julia kotlin lean4 +# lua luau markdown matlab msl +# nix ocaml pascal perl php +# php_phpactor powershell python python_jedi python_ty +# r rego ruby ruby_solargraph rust +# scala solidity swift systemverilog terraform +# toml typescript typescript_vts vue yaml +# zig # (This list may be outdated. For the current list, see values of Language enum here: # https://github.com/oraios/serena/blob/main/src/solidlsp/ls_config.py # For some languages, there are alternative language servers, e.g. csharp_omnisharp, ruby_solargraph.) @@ -65,53 +68,17 @@ read_only: false # list of tool names to exclude. # This extends the existing exclusions (e.g. from the global configuration) -# -# Below is the complete list of tools for convenience. -# To make sure you have the latest list of tools, and to view their descriptions, -# execute `uv run scripts/print_tool_overview.py`. -# -# * `activate_project`: Activates a project by name. -# * `check_onboarding_performed`: Checks whether project onboarding was already performed. -# * `create_text_file`: Creates/overwrites a file in the project directory. -# * `delete_lines`: Deletes a range of lines within a file. -# * `delete_memory`: Deletes a memory from Serena's project-specific memory store. -# * `execute_shell_command`: Executes a shell command. -# * `find_referencing_code_snippets`: Finds code snippets in which the symbol at the given location is referenced. -# * `find_referencing_symbols`: Finds symbols that reference the symbol at the given location (optionally filtered by type). -# * `find_symbol`: Performs a global (or local) search for symbols with/containing a given name/substring (optionally filtered by type). -# * `get_current_config`: Prints the current configuration of the agent, including the active and available projects, tools, contexts, and modes. -# * `get_symbols_overview`: Gets an overview of the top-level symbols defined in a given file. -# * `initial_instructions`: Gets the initial instructions for the current project. -# Should only be used in settings where the system prompt cannot be set, -# e.g. in clients you have no control over, like Claude Desktop. -# * `insert_after_symbol`: Inserts content after the end of the definition of a given symbol. -# * `insert_at_line`: Inserts content at a given line in a file. -# * `insert_before_symbol`: Inserts content before the beginning of the definition of a given symbol. -# * `list_dir`: Lists files and directories in the given directory (optionally with recursion). -# * `list_memories`: Lists memories in Serena's project-specific memory store. -# * `onboarding`: Performs onboarding (identifying the project structure and essential tasks, e.g. for testing or building). -# * `prepare_for_new_conversation`: Provides instructions for preparing for a new conversation (in order to continue with the necessary context). -# * `read_file`: Reads a file within the project directory. -# * `read_memory`: Reads the memory with the given name from Serena's project-specific memory store. -# * `remove_project`: Removes a project from the Serena configuration. -# * `replace_lines`: Replaces a range of lines within a file with new content. -# * `replace_symbol_body`: Replaces the full definition of a symbol. -# * `restart_language_server`: Restarts the language server, may be necessary when edits not through Serena happen. -# * `search_for_pattern`: Performs a search for a pattern in the project. -# * `summarize_changes`: Provides instructions for summarizing the changes made to the codebase. -# * `switch_modes`: Activates modes by providing a list of their names -# * `think_about_collected_information`: Thinking tool for pondering the completeness of collected information. -# * `think_about_task_adherence`: Thinking tool for determining whether the agent is still on track with the current task. -# * `think_about_whether_you_are_done`: Thinking tool for determining whether the task is truly completed. -# * `write_memory`: Writes a named memory (for future reference) to Serena's project-specific memory store. +# Find the list of tools here: https://oraios.github.io/serena/01-about/035_tools.html excluded_tools: [] # list of tools to include that would otherwise be disabled (particularly optional tools that are disabled by default). # This extends the existing inclusions (e.g. from the global configuration). +# Find the list of tools here: https://oraios.github.io/serena/01-about/035_tools.html included_optional_tools: [] # fixed set of tools to use as the base tool set (if non-empty), replacing Serena's default set of tools. # This cannot be combined with non-empty excluded_tools or included_optional_tools. +# Find the list of tools here: https://oraios.github.io/serena/01-about/035_tools.html fixed_tools: [] # list of mode names to that are always to be included in the set of active modes @@ -122,11 +89,14 @@ fixed_tools: [] # Set this to a list of mode names to always include the respective modes for this project. base_modes: -# list of mode names that are to be activated by default. -# The full set of modes to be activated is base_modes + default_modes. -# If the setting is undefined, the default_modes from the global configuration (serena_config.yml) apply. +# list of mode names that are to be activated by default, overriding the setting in the global configuration. +# The full set of modes to be activated is base_modes (from global config) + default_modes + added_modes. +# If the setting is undefined/empty, the default_modes from the global configuration (serena_config.yml) apply. # Otherwise, this overrides the setting from the global configuration (serena_config.yml). +# Therefore, you can set this to [] if you do not want the default modes defined in the global config to apply +# for this project. # This setting can, in turn, be overridden by CLI parameters (--mode). +# See https://oraios.github.io/serena/02-usage/050_configuration.html#modes default_modes: # initial prompt for the project. It will always be given to the LLM upon activating the project @@ -150,3 +120,8 @@ read_only_memory_patterns: [] # Extends the list from the global configuration, merging the two lists. # Example: ["_archive/.*", "_episodes/.*"] ignored_memory_patterns: [] + +# list of mode names to be activated additionally for this project, e.g. ["query-projects"] +# The full set of modes to be activated is base_modes (from global config) + default_modes + added_modes. +# See https://oraios.github.io/serena/02-usage/050_configuration.html#modes +added_modes: diff --git a/Dockerfile b/Dockerfile index 54b1438..0375bac 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,5 @@ # Build stage -FROM node:20-alpine AS builder +FROM node:22-alpine AS builder WORKDIR /app COPY package*.json ./ @@ -7,8 +7,11 @@ RUN npm ci --ignore-scripts && npx nuxt prepare COPY . . RUN npm run build -# Production stage — only the self-contained .output is needed -FROM node:20-alpine +# Production stage — only the self-contained .output is needed. +# bash + curl are added so Dokploy scheduled tasks (which wrap commands in +# `bash -c "..."`) can run; alpine ships only ash and has no curl by default. +FROM node:22-alpine +RUN apk add --no-cache bash curl WORKDIR /app COPY --from=builder /app/.output .output diff --git a/app/assets/css/main.css b/app/assets/css/main.css index 4f1ab74..9ee189f 100644 --- a/app/assets/css/main.css +++ b/app/assets/css/main.css @@ -27,7 +27,10 @@ --text: #2a2015; --text-bright: #1a1008; --text-dim: #5a5040; - --text-faint: #746a58; + /* Darkened from #746a58 (4.01:1 on --surface, fails WCAG AA) to #665c4b + (4.94:1 on --surface, 5.13:1 on --bg). Stays visually quieter than + --text-dim (5.80:1) while meeting AA for small text. */ + --text-faint: #665c4b; --parch: #2a2015; --parch-hover: #3a3025; --parch-text: #ede4d0; @@ -273,6 +276,98 @@ p a, blockquote a { min-width: 0; } +/* ---- Nuxt UI placeholder contrast ---- + Default Nuxt UI placeholder uses `text-dimmed` (#a6a09b) which fails WCAG + AA on cream and white backgrounds (≈2.4:1). Override globally to --text-dim + so USelect/USelectMenu placeholders meet the 4.5:1 ratio. */ +[data-slot="placeholder"] { + color: var(--text-dim); +} + +/* ---- SHARED USelectMenu STYLES ---- + Apply via: + Classes are global (not scoped) because Nuxt UI portals the popup content to body. */ +button.zine-select, +button.timezone-select { + display: flex !important; + width: 100%; + padding: 5px 8px !important; + font-family: "Commit Mono", monospace !important; + font-size: 13px !important; + color: var(--text-bright) !important; + background: var(--input-bg) !important; + border: 1px solid var(--border) !important; + border-radius: 0 !important; + box-shadow: none !important; + outline: none !important; + min-height: 0; + --tw-ring-shadow: 0 0 #0000; + --tw-ring-offset-shadow: 0 0 #0000; + --tw-ring-color: transparent; +} + +button.zine-select:hover, +button.timezone-select:hover { + background: var(--input-bg) !important; +} + +button.zine-select:focus, +button.zine-select:focus-visible, +button.zine-select[aria-expanded="true"], +button.timezone-select:focus, +button.timezone-select:focus-visible, +button.timezone-select[aria-expanded="true"] { + border-color: var(--candle) !important; +} + +.tz-content { + background: var(--input-bg) !important; + border: 1px solid var(--border) !important; + border-radius: 0 !important; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.12) !important; + --tw-ring-shadow: 0 0 #0000 !important; + --tw-ring-offset-shadow: 0 0 #0000 !important; + font-family: "Commit Mono", monospace !important; +} + +.tz-input { + border-bottom: 1px dashed var(--border) !important; +} + +.tz-input input { + font-family: "Commit Mono", monospace !important; + font-size: 13px !important; + color: var(--text-bright) !important; + background: transparent !important; + border-radius: 0 !important; + padding: 6px 8px !important; + box-shadow: none !important; + --tw-ring-shadow: 0 0 #0000 !important; + --tw-ring-offset-shadow: 0 0 #0000 !important; +} + +.tz-item { + font-family: "Commit Mono", monospace !important; + font-size: 13px !important; + color: var(--text) !important; + border-radius: 0 !important; + padding: 6px 8px !important; +} + +.tz-item::before { + border-radius: 0 !important; +} + +.tz-item[data-highlighted]::before, +.tz-item[data-highlighted]:not([data-disabled])::before { + background: var(--surface-hover) !important; +} + +.tz-item[data-highlighted], +.tz-item[data-highlighted]:not([data-disabled]) { + color: var(--text-bright) !important; +} + /* ---- MOBILE ---- */ @media (max-width: 1023px) { body { diff --git a/app/components/AppNavigation.vue b/app/components/AppNavigation.vue index 1add2ef..07cc322 100644 --- a/app/components/AppNavigation.vue +++ b/app/components/AppNavigation.vue @@ -25,11 +25,6 @@ /> -
  • - Sign out -
  • @@ -138,11 +133,11 @@