diff --git a/.forgejo/workflows/test.yml b/.forgejo/workflows/test.yml index 2a29c40..8edaae1 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://mongo-ci:27017/ghostguild-test + MONGODB_URI: mongodb://localhost: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,35 +39,15 @@ 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 > /tmp/server.log 2>&1 & + 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' - - name: Server log on failure - if: failure() - run: cat /tmp/server.log || true - - run: npx playwright test - - uses: actions/upload-artifact@v3 + - run: npx playwright test --ignore-snapshots + - uses: actions/upload-artifact@v4 if: failure() with: name: playwright-report @@ -88,3 +68,39 @@ 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/.serena/project.yml b/.serena/project.yml index ddf640a..9d24cb3 100644 --- a/.serena/project.yml +++ b/.serena/project.yml @@ -3,18 +3,15 @@ project_name: "ghostguild-org" # list of languages for which language servers are started; choose from: -# 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 +# 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 # (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.) @@ -68,17 +65,53 @@ read_only: false # list of tool names to exclude. # This extends the existing exclusions (e.g. from the global configuration) -# Find the list of tools here: https://oraios.github.io/serena/01-about/035_tools.html +# +# 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. 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 @@ -89,14 +122,11 @@ 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, 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. +# 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. # 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 @@ -120,8 +150,3 @@ 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/app/assets/css/main.css b/app/assets/css/main.css index 9ee189f..4167651 100644 --- a/app/assets/css/main.css +++ b/app/assets/css/main.css @@ -27,10 +27,7 @@ --text: #2a2015; --text-bright: #1a1008; --text-dim: #5a5040; - /* 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; + --text-faint: #746a58; --parch: #2a2015; --parch-hover: #3a3025; --parch-text: #ede4d0; diff --git a/app/components/BoardPostCard.vue b/app/components/BoardPostCard.vue index a79a535..ff6e9d4 100644 --- a/app/components/BoardPostCard.vue +++ b/app/components/BoardPostCard.vue @@ -158,7 +158,7 @@ const slackLinks = computed(() => { diff --git a/app/components/EventsMiniSidebar.vue b/app/components/EventsMiniSidebar.vue index df953d6..de6066d 100644 --- a/app/components/EventsMiniSidebar.vue +++ b/app/components/EventsMiniSidebar.vue @@ -104,7 +104,7 @@ const formatDate = (dateStr) => { } .em-circle { - font-size: 10px; + font-size: 9px; letter-spacing: 0.06em; text-transform: uppercase; margin-top: 2px; diff --git a/app/components/FilterBar.vue b/app/components/FilterBar.vue index c63f40e..688de8e 100644 --- a/app/components/FilterBar.vue +++ b/app/components/FilterBar.vue @@ -22,7 +22,7 @@ defineEmits(['update:modelValue']) diff --git a/app/components/LoginModal.vue b/app/components/LoginModal.vue index e3fd0b6..67bc904 100644 --- a/app/components/LoginModal.vue +++ b/app/components/LoginModal.vue @@ -40,7 +40,7 @@ type="email" placeholder="your.email@example.com" required - > + />
@@ -182,7 +182,7 @@ onUnmounted(() => document.removeEventListener('keydown', handleKeydown)) .modal-overline { font-family: 'Brygada 1918', serif; - font-size: 13px; + font-size: 14px; font-weight: 600; color: var(--candle); margin-bottom: 12px; @@ -218,7 +218,7 @@ onUnmounted(() => document.removeEventListener('keydown', handleKeydown)) .info-box { font-size: 11px; color: var(--text-faint); - padding: 12px 16px; + padding: 10px 14px; border: 1px dashed var(--border); margin-bottom: 16px; line-height: 1.6; diff --git a/app/components/NaturalDateInput.vue b/app/components/NaturalDateInput.vue index 4e97e05..c2d1130 100644 --- a/app/components/NaturalDateInput.vue +++ b/app/components/NaturalDateInput.vue @@ -18,14 +18,12 @@ @@ -33,8 +31,7 @@
@@ -44,8 +41,7 @@
@@ -55,7 +51,7 @@
- + Use traditional date picker
diff --git a/app/components/OnboardingWidget.vue b/app/components/OnboardingWidget.vue index 3f9f11b..97c246d 100644 --- a/app/components/OnboardingWidget.vue +++ b/app/components/OnboardingWidget.vue @@ -118,7 +118,7 @@ const barEmpty = computed(() => '-'.repeat((4 - completedCount.value) * 2) + ']' display: inline-block; margin-top: 8px; padding: 4px 12px; - border: 1px dashed color-mix(in srgb, var(--parch-text) 25%, transparent); + border: 1px dashed rgba(237, 228, 208, 0.25); color: var(--parch-accent); font-size: 11px; text-decoration: none; @@ -134,7 +134,7 @@ const barEmpty = computed(() => '-'.repeat((4 - completedCount.value) * 2) + ']' .ow-progress { margin-top: 10px; padding-top: 8px; - border-top: 1px dashed color-mix(in srgb, var(--parch-text) 12%, transparent); + border-top: 1px dashed rgba(237, 228, 208, 0.12); font-size: 11px; color: var(--parch-text-dim); display: flex; @@ -153,7 +153,7 @@ const barEmpty = computed(() => '-'.repeat((4 - completedCount.value) * 2) + ']' } .ow-bar-empty { - color: color-mix(in srgb, var(--parch-text) 20%, transparent); + color: rgba(237, 228, 208, 0.2); } .ow-skip { diff --git a/app/components/SeriesPassPurchase.vue b/app/components/SeriesPassPurchase.vue index fff5fd4..ef293af 100644 --- a/app/components/SeriesPassPurchase.vue +++ b/app/components/SeriesPassPurchase.vue @@ -9,11 +9,14 @@
-
-

+
+

Unable to Load Series Pass

-

{{ error }}

+

{{ error }}

@@ -45,7 +48,7 @@

{{ @@ -100,20 +103,18 @@
-
+
Member Benefit
-
+
This series pass is free for Ghost Guild members!
@@ -307,7 +308,7 @@ const handleSubmit = async () => { title: "Series Pass Purchased!", description: `You're now registered for all ${purchaseResponse.registration.eventsRegistered} events in this series.`, color: "green", - duration: 5000, + timeout: 5000, }); // Emit success event @@ -327,7 +328,7 @@ const handleSubmit = async () => { title: "Purchase Failed", description: errorMessage, color: "red", - duration: 5000, + timeout: 5000, }); emit("purchase-error", errorMessage); @@ -354,18 +355,3 @@ const formatPrice = (price, currency = "CAD") => { }).format(price); }; - - diff --git a/app/components/SignupFlowOverlay.vue b/app/components/SignupFlowOverlay.vue index 10fe663..6ad3321 100644 --- a/app/components/SignupFlowOverlay.vue +++ b/app/components/SignupFlowOverlay.vue @@ -108,7 +108,7 @@ const stepLabel = computed(() => { position: fixed; inset: 0; z-index: 50; - background: color-mix(in srgb, var(--parch) 72%, transparent); + background: rgba(42, 32, 21, 0.72); backdrop-filter: blur(4px); display: flex; align-items: center; diff --git a/app/components/TierPicker.vue b/app/components/TierPicker.vue new file mode 100644 index 0000000..5cf6318 --- /dev/null +++ b/app/components/TierPicker.vue @@ -0,0 +1,99 @@ + + + + + diff --git a/app/composables/useMemberPayment.js b/app/composables/useMemberPayment.js index fcab6fe..23255a7 100644 --- a/app/composables/useMemberPayment.js +++ b/app/composables/useMemberPayment.js @@ -25,45 +25,17 @@ export const useMemberPayment = () => { paymentSuccess.value = false try { - // Fast-path: when both Helcim ids are already cached on the member doc - // AND a card's on file, we can skip the paid getOrCreateCustomer round - // trip entirely and go straight to subscription creation. - const hasCachedHelcimIds = Boolean( - memberData.value?.helcimCustomerId && memberData.value?.helcimCustomerCode - ) - - let existing = null - let probedExistingCard = false - let cardToken = null - - if (hasCachedHelcimIds) { - existing = await $fetch('/api/helcim/existing-card').catch((err) => { + // Skip HelcimPay verify if a card's already on file — Helcim refuses + // to re-save it, breaking retries after a partial-failed signup. + const [, existing] = await Promise.all([ + getOrCreateCustomer(), + $fetch('/api/helcim/existing-card').catch((err) => { console.warn('[payment] existing-card lookup failed, falling back to verify flow:', err) return null - }) - probedExistingCard = true - if (existing?.cardToken) { - customerId.value = memberData.value.helcimCustomerId - customerCode.value = memberData.value.helcimCustomerCode - cardToken = existing.cardToken - } - } + }), + ]) - if (!cardToken) { - // Skip HelcimPay verify if a card's already on file — Helcim refuses - // to re-save it, breaking retries after a partial-failed signup. - const [, existingFromFull] = await Promise.all([ - getOrCreateCustomer(), - probedExistingCard - ? Promise.resolve(existing) - : $fetch('/api/helcim/existing-card').catch((err) => { - console.warn('[payment] existing-card lookup failed, falling back to verify flow:', err) - return null - }), - ]) - - cardToken = existingFromFull?.cardToken || null - } + let cardToken = existing?.cardToken || null if (!cardToken) { await initializeHelcimPay( diff --git a/app/config/memberStatus.js b/app/config/memberStatus.js deleted file mode 100644 index 04850e8..0000000 --- a/app/config/memberStatus.js +++ /dev/null @@ -1,8 +0,0 @@ -export const STATUS_LABELS = { - active: "Active", - pending_payment: "Payment setup incomplete", - suspended: "Paused", - cancelled: "Closed", -}; - -export const statusLabel = (s) => STATUS_LABELS[s] || "Pending"; diff --git a/app/middleware/coming-soon.global.js b/app/middleware/coming-soon.global.js index d2433f8..c1ee747 100644 --- a/app/middleware/coming-soon.global.js +++ b/app/middleware/coming-soon.global.js @@ -21,15 +21,6 @@ export default defineNuxtRouteMiddleware(async (to, from) => { return; } - // Logged-in admins bypass coming-soon (and see the public site + their dashboard) - try { - const headers = import.meta.server ? useRequestHeaders(["cookie"]) : undefined; - const member = await $fetch("/api/auth/member", { headers }); - if (member?.role === "admin") return; - } catch { - // Not authenticated — fall through to redirect - } - // Redirect all other routes to coming-soon return navigateTo("/coming-soon"); }); diff --git a/app/pages/about.vue b/app/pages/about.vue index 5920e9b..a423811 100644 --- a/app/pages/about.vue +++ b/app/pages/about.vue @@ -38,16 +38,16 @@
-

Community

+

Community

For anyone exploring cooperative models.

-

Founder

+

Founder

For people actively building cooperatives.

-

Practitioner

+

Practitioner

For experienced practitioners sharing what they know.

diff --git a/app/pages/admin/events/index.vue b/app/pages/admin/events/index.vue index 0cafd01..051e4c8 100644 --- a/app/pages/admin/events/index.vue +++ b/app/pages/admin/events/index.vue @@ -570,7 +570,7 @@ tbody td { letter-spacing: 0.04em; text-transform: uppercase; color: var(--c-founder); - border: 1px dashed color-mix(in srgb, var(--ember) 30%, transparent); + border: 1px dashed rgba(138, 68, 32, 0.3); padding: 2px 8px; } @@ -583,7 +583,7 @@ tbody td { font-size: 10px; font-weight: 600; color: var(--c-founder); - border: 1px dashed color-mix(in srgb, var(--ember) 40%, transparent); + border: 1px dashed rgba(138, 68, 32, 0.4); border-radius: 50%; } @@ -632,12 +632,12 @@ tbody td { .status-upcoming { color: var(--candle); - border-color: color-mix(in srgb, var(--candle) 30%, transparent); + border-color: rgba(122, 90, 16, 0.3); } .status-ongoing { color: var(--green); - border-color: color-mix(in srgb, var(--green) 30%, transparent); + border-color: rgba(74, 106, 56, 0.3); } .status-past { @@ -647,7 +647,7 @@ tbody td { .status-cancelled { color: var(--ember); - border-color: color-mix(in srgb, var(--ember) 30%, transparent); + border-color: rgba(138, 68, 32, 0.3); margin-top: 4px; } diff --git a/app/pages/admin/index.vue b/app/pages/admin/index.vue index 289a813..e117a8f 100644 --- a/app/pages/admin/index.vue +++ b/app/pages/admin/index.vue @@ -65,7 +65,7 @@ {{ member.email }}
- + {{ member.circle }} {{ formatDate(member.createdAt) }}
diff --git a/app/pages/admin/members/[id].vue b/app/pages/admin/members/[id].vue index e082be7..4d8be5d 100644 --- a/app/pages/admin/members/[id].vue +++ b/app/pages/admin/members/[id].vue @@ -16,7 +16,7 @@

{{ member.email }}

- + {{ member.circle }} {{ member.status }}
@@ -39,11 +39,11 @@
- +
- +
@@ -56,18 +56,14 @@
-

- Writes to our database only. If the member is on a paid plan, also update recurringAmount in the Helcim dashboard — this form does not sync. -

@@ -110,19 +106,8 @@
Slack invite
-
- Invited {{ formatDate(member.slackInvitedAt) }} -
-
- Not yet invited - +
+ {{ member.slackInvited ? "Invited" : "Pending" }}
@@ -170,6 +155,12 @@ {{ member.onboarding?.completedAt ? formatDate(member.onboarding.completedAt) : 'In progress' }}
+
+
Slack status
+
+ {{ member.slackInviteStatus || 'none' }} +
+
@@ -243,7 +234,6 @@