Skip to content

refactor: migrate account emails to dedicated api#1652

Closed
arpitgupta1214 wants to merge 2 commits intotestfrom
codex/account-emails-chat
Closed

refactor: migrate account emails to dedicated api#1652
arpitgupta1214 wants to merge 2 commits intotestfrom
codex/account-emails-chat

Conversation

@arpitgupta1214
Copy link
Copy Markdown
Collaborator

@arpitgupta1214 arpitgupta1214 commented Apr 7, 2026

Summary

  • switch account email lookups to the dedicated API endpoint
  • remove the old local /api/account-emails route
  • use the new account-scoped account_id contract from the frontend

Testing

  • pnpm test lib/accounts/tests/fetchAccountEmails.test.ts

Summary by cubic

Migrated account email lookups to the external accounts API and removed the local /api/account-emails route. Added a typed fetcher and a simplified hook that uses a Privy bearer token; updated FileInfoDialog and TasksList to use it.

  • Refactors
    • Removed app/api/account-emails/route.ts.
    • Added lib/accounts/fetchAccountEmails and hooks/useAccountEmails to call GET /api/accounts/emails with Authorization: Bearer <token> and repeated account_id params.
    • Simplified useAccountEmails: takes accountIds (and optional enabled), gates on Privy authentication, and uses cleaner query keys; updated FileInfoDialog and TasksList accordingly.

Written for commit 130d00f. Summary will update on new commits.

@vercel
Copy link
Copy Markdown
Contributor

vercel bot commented Apr 7, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
recoup-chat Ready Ready Preview Apr 7, 2026 9:59pm

Request Review

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Apr 7, 2026

Warning

Rate limit exceeded

@arpitgupta1214 has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 2 minutes and 53 seconds before requesting another review.

Your organization is not enrolled in usage-based pricing. Contact your admin to enable usage-based pricing to continue reviews beyond the rate limit, or try again in 2 minutes and 53 seconds.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: e229ff7c-9f8f-4ceb-81ac-ae06762f914b

📥 Commits

Reviewing files that changed from the base of the PR and between 04f4dfe and 130d00f.

📒 Files selected for processing (5)
  • app/api/account-emails/route.ts
  • components/Files/FileInfoDialog.tsx
  • components/TasksPage/TasksList.tsx
  • hooks/useAccountEmails.ts
  • lib/accounts/fetchAccountEmails.ts
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch codex/account-emails-chat

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@arpitgupta1214 arpitgupta1214 force-pushed the codex/account-emails-chat branch from a356f2b to d354453 Compare April 7, 2026 21:52
Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

11 issues found across 35 files

Confidence score: 1/5

  • High-risk security exposure: lib/api/elysia/routes/youtube/channelInfo.ts accepts artist_account_id from query params without ownership checks, enabling IDOR access to other users' accounts.
  • API-key auth can fall through to cookie auth in lib/api/elysia/plugins/auth.ts, so failed API-key validation may still authenticate, which is a serious auth bypass risk.
  • Given the two concrete auth/authorization flaws (both high severity), this is likely to break security expectations and is not safe to merge yet.
  • Pay close attention to lib/api/elysia/routes/youtube/channelInfo.ts and lib/api/elysia/plugins/auth.ts - IDOR risk and API-key auth fallback need to be fixed.
Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="lib/api/elysia/routes/youtube/channelInfo.ts">

<violation number="1" location="lib/api/elysia/routes/youtube/channelInfo.ts:9">
P2: Custom agent: **Module should export a single primary function whose name matches the filename**

Exported name `youtubeChannelInfoRoute` doesn't match the file basename `channelInfo`. Since the file already lives under `routes/youtube/`, the `youtube` prefix is redundant. Rename the export to `channelInfoRoute` (and update the import in the sibling `index.ts`).</violation>

<violation number="2" location="lib/api/elysia/routes/youtube/channelInfo.ts:15">
P0: IDOR vulnerability: `artist_account_id` from query params is passed directly to `validateYouTubeTokens` without verifying the authenticated user owns or has access to that account. The `user` object from `authPlugin` is available but never checked. Any authenticated user can fetch YouTube tokens for any account.</violation>

<violation number="3" location="lib/api/elysia/routes/youtube/channelInfo.ts:19">
P2: Passing an empty string `""` for `refreshToken` when `tokens.refresh_token` is null/undefined. Since the parameter is optional (`refreshToken?: string`), pass `undefined` instead to avoid the downstream OAuth client treating an empty string as a valid refresh token.</violation>
</file>

<file name="hooks/useYoutubeStatus.ts">

<violation number="1" location="hooks/useYoutubeStatus.ts:16">
P1: Custom agent: **Flag AI Slop and Fabricated Changes**

This change drops the `tokenStatus === "valid"` check, so any non-empty channel array now counts as `"valid"` — even when the underlying token is expired or revoked. No test covers the new behavior, and a regression assertion (e.g., verifying that an array of channels with an invalid token still returns `"invalid"`) would be straightforward to add.</violation>
</file>

<file name="lib/api/elysia/routes/agentTemplates/favorites.ts">

<violation number="1" location="lib/api/elysia/routes/agentTemplates/favorites.ts:22">
P2: Remove `console.error` — the project's "No Production Logging" principle (CLAUDE.md) disallows console statements in merged code.</violation>
</file>

<file name="lib/youtube/token-refresher.ts">

<violation number="1" location="lib/youtube/token-refresher.ts:77">
P1: Specific errors thrown inside the `try` block (`REFRESH_INCOMPLETE_CREDENTIALS`, `DB_UPDATE_FAILED`) are caught by the `catch` and silently replaced with `REFRESH_GENERAL_FAILURE`. Add a re-throw guard at the top of the catch block to preserve errors already thrown with specific messages.</violation>
</file>

<file name="lib/supabase/account_api_keys/getAccountIdFromApiKey.ts">

<violation number="1" location="lib/supabase/account_api_keys/getAccountIdFromApiKey.ts:21">
P2: `.limit(1)` before `.maybeSingle()` silently masks duplicate `key_hash` rows. Without `limit(1)`, Supabase would error on >1 matching rows, surfacing a data-integrity problem. Drop `limit(1)` so duplicates are caught instead of hidden.</violation>
</file>

<file name="components/Agents/useAgentToggleFavorite.ts">

<violation number="1" location="components/Agents/useAgentToggleFavorite.ts:12">
P2: Removing the user-authentication guard means unauthenticated calls now hit the network and surface a generic error toast ("Failed to update favorite") instead of being silently skipped. Consider adding a session/auth check (e.g., from your auth provider) so the function early-returns before making a pointless request.</violation>
</file>

<file name="lib/api/elysia/plugins/auth.ts">

<violation number="1" location="lib/api/elysia/plugins/auth.ts:18">
P1: Security: failed API-key auth silently falls through to cookie auth. When a caller explicitly provides `x-api-key`, a validation failure should return 401 immediately rather than trying the privy-token cookie. Otherwise a request intended for one account could be authenticated as a different account via the cookie, creating a confused-deputy scenario.</violation>
</file>

<file name="lib/youtube/token-validator.ts">

<violation number="1" location="lib/youtube/token-validator.ts:42">
P2: Intentional validation throws (`NO_TOKENS`, `EXPIRED_TOKENS_NO_REFRESH`) are inside the `try` block and will be caught, misleadingly logged as errors via `console.error`, then re-thrown. This pollutes error logs with expected validation outcomes and double-logs refresh failures (since `refreshStoredYouTubeToken` already logs its own errors). Narrow the `try` to only wrap the database call, or re-throw known errors before logging.</violation>
</file>

<file name="lib/youtube/channel-fetcher.ts">

<violation number="1" location="lib/youtube/channel-fetcher.ts:36">
P2: This `throw` is inside the `try` block and gets caught by the function's own `catch` handler. The error is logged via `console.error` (misleading for a business-logic "no channels" case) and then re-wrapped into a new `Error`, losing the original stack trace. Move this throw before the try block, or restructure so this case isn't caught by the generic error handler — e.g., check for empty items after the try/catch, or re-throw known errors in the catch without wrapping.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review, or fix all with cubic.

"/channel-info",
async ({ query, status }) => {
try {
const tokens = await validateYouTubeTokens(query.artist_account_id);
Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai bot Apr 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P0: IDOR vulnerability: artist_account_id from query params is passed directly to validateYouTubeTokens without verifying the authenticated user owns or has access to that account. The user object from authPlugin is available but never checked. Any authenticated user can fetch YouTube tokens for any account.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At lib/api/elysia/routes/youtube/channelInfo.ts, line 15:

<comment>IDOR vulnerability: `artist_account_id` from query params is passed directly to `validateYouTubeTokens` without verifying the authenticated user owns or has access to that account. The `user` object from `authPlugin` is available but never checked. Any authenticated user can fetch YouTube tokens for any account.</comment>

<file context>
@@ -0,0 +1,48 @@
+    "/channel-info",
+    async ({ query, status }) => {
+      try {
+        const tokens = await validateYouTubeTokens(query.artist_account_id);
+
+        return await fetchYouTubeChannelInfo({
</file context>
Fix with Cubic

return channelResponse.tokenStatus === "valid"
? "valid"
: "invalid";
if (Array.isArray(channelResponse) && channelResponse.length > 0) {
Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai bot Apr 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: Custom agent: Flag AI Slop and Fabricated Changes

This change drops the tokenStatus === "valid" check, so any non-empty channel array now counts as "valid" — even when the underlying token is expired or revoked. No test covers the new behavior, and a regression assertion (e.g., verifying that an array of channels with an invalid token still returns "invalid") would be straightforward to add.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At hooks/useYoutubeStatus.ts, line 16:

<comment>This change drops the `tokenStatus === "valid"` check, so any non-empty channel array now counts as `"valid"` — even when the underlying token is expired or revoked. No test covers the new behavior, and a regression assertion (e.g., verifying that an array of channels with an invalid token still returns `"invalid"`) would be straightforward to add.</comment>

<file context>
@@ -13,10 +13,8 @@ const useYoutubeStatus = (artistAccountId?: string) => {
-            return channelResponse.tokenStatus === "valid"
-              ? "valid"
-              : "invalid";
+          if (Array.isArray(channelResponse) && channelResponse.length > 0) {
+            return "valid";
           }
</file context>
Fix with Cubic

'REFRESH_GENERAL_FAILURE',
YouTubeErrorMessages.REFRESH_GENERAL_FAILURE
);
throw new Error(YouTubeErrorMessages.REFRESH_GENERAL_FAILURE);
Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai bot Apr 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: Specific errors thrown inside the try block (REFRESH_INCOMPLETE_CREDENTIALS, DB_UPDATE_FAILED) are caught by the catch and silently replaced with REFRESH_GENERAL_FAILURE. Add a re-throw guard at the top of the catch block to preserve errors already thrown with specific messages.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At lib/youtube/token-refresher.ts, line 77:

<comment>Specific errors thrown inside the `try` block (`REFRESH_INCOMPLETE_CREDENTIALS`, `DB_UPDATE_FAILED`) are caught by the `catch` and silently replaced with `REFRESH_GENERAL_FAILURE`. Add a re-throw guard at the top of the catch block to preserve errors already thrown with specific messages.</comment>

<file context>
@@ -92,16 +70,10 @@ export async function refreshStoredYouTubeToken(
-      'REFRESH_GENERAL_FAILURE', 
-      YouTubeErrorMessages.REFRESH_GENERAL_FAILURE
-    );
+    throw new Error(YouTubeErrorMessages.REFRESH_GENERAL_FAILURE);
   }
-}
</file context>
Fix with Cubic

identifier: apiKey,
},
};
} catch (error) {
Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai bot Apr 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: Security: failed API-key auth silently falls through to cookie auth. When a caller explicitly provides x-api-key, a validation failure should return 401 immediately rather than trying the privy-token cookie. Otherwise a request intended for one account could be authenticated as a different account via the cookie, creating a confused-deputy scenario.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At lib/api/elysia/plugins/auth.ts, line 18:

<comment>Security: failed API-key auth silently falls through to cookie auth. When a caller explicitly provides `x-api-key`, a validation failure should return 401 immediately rather than trying the privy-token cookie. Otherwise a request intended for one account could be authenticated as a different account via the cookie, creating a confused-deputy scenario.</comment>

<file context>
@@ -0,0 +1,37 @@
+            identifier: apiKey,
+          },
+        };
+      } catch (error) {
+        console.error("Elysia auth apiKey validation failed:", error);
+      }
</file context>
Fix with Cubic


const errorResponseSchema = t.String();

export const youtubeChannelInfoRoute = new Elysia()
Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai bot Apr 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Custom agent: Module should export a single primary function whose name matches the filename

Exported name youtubeChannelInfoRoute doesn't match the file basename channelInfo. Since the file already lives under routes/youtube/, the youtube prefix is redundant. Rename the export to channelInfoRoute (and update the import in the sibling index.ts).

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At lib/api/elysia/routes/youtube/channelInfo.ts, line 9:

<comment>Exported name `youtubeChannelInfoRoute` doesn't match the file basename `channelInfo`. Since the file already lives under `routes/youtube/`, the `youtube` prefix is redundant. Rename the export to `channelInfoRoute` (and update the import in the sibling `index.ts`).</comment>

<file context>
@@ -0,0 +1,48 @@
+
+const errorResponseSchema = t.String();
+
+export const youtubeChannelInfoRoute = new Elysia()
+  .use(authPlugin)
+  .get(
</file context>
Fix with Cubic

.from("account_api_keys")
.select("account")
.eq("key_hash", keyHash)
.limit(1)
Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai bot Apr 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: .limit(1) before .maybeSingle() silently masks duplicate key_hash rows. Without limit(1), Supabase would error on >1 matching rows, surfacing a data-integrity problem. Drop limit(1) so duplicates are caught instead of hidden.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At lib/supabase/account_api_keys/getAccountIdFromApiKey.ts, line 21:

<comment>`.limit(1)` before `.maybeSingle()` silently masks duplicate `key_hash` rows. Without `limit(1)`, Supabase would error on >1 matching rows, surfacing a data-integrity problem. Drop `limit(1)` so duplicates are caught instead of hidden.</comment>

<file context>
@@ -0,0 +1,36 @@
+    .from("account_api_keys")
+    .select("account")
+    .eq("key_hash", keyHash)
+    .limit(1)
+    .maybeSingle();
+
</file context>
Fix with Cubic


return await fetchYouTubeChannelInfo({
accessToken: tokens.access_token,
refreshToken: tokens.refresh_token || "",
Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai bot Apr 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Passing an empty string "" for refreshToken when tokens.refresh_token is null/undefined. Since the parameter is optional (refreshToken?: string), pass undefined instead to avoid the downstream OAuth client treating an empty string as a valid refresh token.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At lib/api/elysia/routes/youtube/channelInfo.ts, line 19:

<comment>Passing an empty string `""` for `refreshToken` when `tokens.refresh_token` is null/undefined. Since the parameter is optional (`refreshToken?: string`), pass `undefined` instead to avoid the downstream OAuth client treating an empty string as a valid refresh token.</comment>

<file context>
@@ -0,0 +1,48 @@
+
+        return await fetchYouTubeChannelInfo({
+          accessToken: tokens.access_token,
+          refreshToken: tokens.refresh_token || "",
+          includeBranding: true,
+        });
</file context>
Fix with Cubic

nextFavourite: boolean
) => {
if (!userData?.id || !templateId) return;
if (!templateId) return;
Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai bot Apr 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Removing the user-authentication guard means unauthenticated calls now hit the network and surface a generic error toast ("Failed to update favorite") instead of being silently skipped. Consider adding a session/auth check (e.g., from your auth provider) so the function early-returns before making a pointless request.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At components/Agents/useAgentToggleFavorite.ts, line 12:

<comment>Removing the user-authentication guard means unauthenticated calls now hit the network and surface a generic error toast ("Failed to update favorite") instead of being silently skipped. Consider adding a session/auth check (e.g., from your auth provider) so the function early-returns before making a pointless request.</comment>

<file context>
@@ -1,22 +1,19 @@
     nextFavourite: boolean
   ) => {
-    if (!userData?.id || !templateId) return;
+    if (!templateId) return;
     
     try {
</file context>
Fix with Cubic

} catch (error) {
console.error('Error validating YouTube tokens:', error);
return YouTubeErrorBuilder.createUtilityError('FETCH_ERROR', YouTubeErrorMessages.FETCH_ERROR);
console.error("Error validating YouTube tokens:", error);
Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai bot Apr 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Intentional validation throws (NO_TOKENS, EXPIRED_TOKENS_NO_REFRESH) are inside the try block and will be caught, misleadingly logged as errors via console.error, then re-thrown. This pollutes error logs with expected validation outcomes and double-logs refresh failures (since refreshStoredYouTubeToken already logs its own errors). Narrow the try to only wrap the database call, or re-throw known errors before logging.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At lib/youtube/token-validator.ts, line 42:

<comment>Intentional validation throws (`NO_TOKENS`, `EXPIRED_TOKENS_NO_REFRESH`) are inside the `try` block and will be caught, misleadingly logged as errors via `console.error`, then re-thrown. This pollutes error logs with expected validation outcomes and double-logs refresh failures (since `refreshStoredYouTubeToken` already logs its own errors). Narrow the `try` to only wrap the database call, or re-throw known errors before logging.</comment>

<file context>
@@ -1,61 +1,50 @@
   } catch (error) {
-    console.error('Error validating YouTube tokens:', error);
-    return YouTubeErrorBuilder.createUtilityError('FETCH_ERROR', YouTubeErrorMessages.FETCH_ERROR);
+    console.error("Error validating YouTube tokens:", error);
+
+    if (error instanceof Error) {
</file context>
Fix with Cubic

"NO_CHANNELS",
YouTubeErrorMessages.NO_CHANNELS
);
throw new Error(YouTubeErrorMessages.NO_CHANNELS);
Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai bot Apr 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: This throw is inside the try block and gets caught by the function's own catch handler. The error is logged via console.error (misleading for a business-logic "no channels" case) and then re-wrapped into a new Error, losing the original stack trace. Move this throw before the try block, or restructure so this case isn't caught by the generic error handler — e.g., check for empty items after the try/catch, or re-throw known errors in the catch without wrapping.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At lib/youtube/channel-fetcher.ts, line 36:

<comment>This `throw` is inside the `try` block and gets caught by the function's own `catch` handler. The error is logged via `console.error` (misleading for a business-logic "no channels" case) and then re-wrapped into a new `Error`, losing the original stack trace. Move this throw before the try block, or restructure so this case isn't caught by the generic error handler — e.g., check for empty items after the try/catch, or re-throw known errors in the catch without wrapping.</comment>

<file context>
@@ -39,10 +33,7 @@ export async function fetchYouTubeChannelInfo({
-        "NO_CHANNELS",
-        YouTubeErrorMessages.NO_CHANNELS
-      );
+      throw new Error(YouTubeErrorMessages.NO_CHANNELS);
     }
 
</file context>
Fix with Cubic

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: a356f2bc5d

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment on lines +7 to +8
const apiKey = request.headers.get("x-api-key");
const privyToken = cookie["privy-token"]?.value as string | undefined;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Support Bearer auth in Elysia auth plugin

The new auth resolver only reads x-api-key and a privy-token cookie, so requests that authenticate with Authorization: Bearer ... cannot be resolved to a user. This is a regression risk for the app’s existing Privy pattern (client code typically fetches an access token and sends it as a Bearer header), and it can make /api/youtube/channel-info and /api/agent-templates/favorites return 401 for logged-in users in environments where that cookie is not present.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

0 issues found across 5 files (changes from recent commits).

Requires human review: Auto-approval blocked by 11 unresolved issues from previous reviews.

@arpitgupta1214
Copy link
Copy Markdown
Collaborator Author

Closing this PR to reopen from a fresh branch so the review thread starts clean without the stale Elysia-related comments.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant