Skip to content

Feature/renew style - #21

Merged
AndrewHanasiro merged 5 commits into
mainfrom
feature/renew-style
Apr 27, 2026
Merged

Feature/renew style#21
AndrewHanasiro merged 5 commits into
mainfrom
feature/renew-style

Conversation

@AndrewHanasiro

@AndrewHanasiro AndrewHanasiro commented Apr 26, 2026

Copy link
Copy Markdown
Member

Summary by CodeRabbit

  • New Features

    • Expanded multi-factor authentication flows (TOTP with QR, Email, Phone) and activation UI.
  • UI Improvements

    • Redesigned login, MFA, invoices, users, and homepage with responsive Tailwind styling and updated navigation that shows contextual items when signed in.
    • Global stylesheet and PostCSS/Tailwind integration added.
  • Bug Fixes

    • Corrected invoice list parsing.
  • Tests

    • Added end-to-end login flow tests; removed obsolete header test.

@coderabbitai

coderabbitai Bot commented Apr 26, 2026

Copy link
Copy Markdown

Warning

Rate limit exceeded

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

To keep reviews running without waiting, you can enable usage-based add-on for your organization. This allows additional reviews beyond the hourly cap. Account admins can enable it under billing.

⌛ 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: defaults

Review profile: CHILL

Plan: Pro

Run ID: 0f366c3e-ea82-4ce6-a217-ff5030537d9d

📥 Commits

Reviewing files that changed from the base of the PR and between 7703ec2 and c7c6d79.

📒 Files selected for processing (1)
  • e2e/Login.test.ts
📝 Walkthrough

Walkthrough

Adds Tailwind/PostCSS, updates Prettier and tooling, migrates auth endpoints to PUBLIC_AUTH_URL, overhauls MFA flow with strategy-specific modals (TOTP/Email/Phone) and API changes, removes Header, converts many UI components from SCSS to Tailwind, and normalizes formatting from tabs to spaces.

Changes

Cohort / File(s) Summary
Tooling & Config
/.prettierignore, /.prettierrc, postcss.config.js, tailwind.config.ts, package.json, eslint.config.js, playwright.config.ts, vite.config.ts, svelte.config.js, tsconfig.json, .github/workflows/*
Add Tailwind/PostCSS config and plugin, change Prettier options (no tabs, bracket settings), add prettier-plugin-tailwindcss, normalize indentation, and inject PUBLIC_AUTH_URL into CI/playwright workflows.
Global Styles & Assets
src/app.css, src/app.html, src/app.d.ts, src/ambient.d.ts
Add Tailwind layer imports to app.css; reindent/template whitespace adjustments.
Layout & Navigation
src/routes/+layout.svelte, src/routes/Header.svelte (deleted), src/routes/Menu.svelte
Remove Header, always render Menu in layout, convert layout/menu to Tailwind and simplify credential-driven navigation/logout using $credential.
Home / Landing
src/routes/+page.svelte
Replace SCSS static page with Tailwind layout, add typed Exhibits data and dynamic exhibit cards (Monetization added).
Authentication Store & API clients
src/stores/auth.ts, src/routes/mfa/mfa.ts, src/routes/users/users.ts, src/routes/invoices/invoices.ts
Switch to PUBLIC_AUTH_URL; change createMfa signature (adds code/secret) and add verifyMfa; refactor credential store internals and request URL construction.
MFA UI & Modals
src/routes/mfa/+page.svelte, src/routes/mfa/modal.svelte, src/routes/mfa/strategy.svelte, src/routes/mfa/_modals/totp.svelte, .../email.svelte, .../phone.svelte
Introduce strategy-specific modals (TOTP generates otpauth QR via otpauth + qrcode), change modal props/flow to setChoose/strategyChoosed/alreadySetup, and move verification/creation flows into new interactions.
Login Flow
src/routes/login/+page.svelte, .../default.svelte, .../choose.svelte, .../code.svelte
Convert to Tailwind, update form submit handlers to prevent default, add stricter input constraints (e.g., maxlength on code), and preserve MFA branching.
Invoices & Users UI
src/routes/invoices/*, src/routes/users/*
Convert pages and table components to Tailwind; adjust fetch flows to use PUBLIC_AUTH_URL; fix invoice response typo (invocesinvoices).
Tests & E2E
e2e/Login.test.ts, e2e/Header.test.ts, test/Header.svelte.test.ts, test/Menu.svelte.test.ts
Add Playwright e2e Login suite; remove header tests and update Menu tests to reflect removed header and new login-state behavior; some assertions removed/modified.
Misc / Formatting
many src/* files, configs, deleted src/routes/Header.svelte
Large set of file reindents (tabs→spaces) and multiple components refactored from SCSS to Tailwind classes; minor fixes and deletions across various components.

Sequence Diagram(s)

sequenceDiagram
    autonumber
    actor User
    participant UI as Client UI
    participant Modal as MFA Modal
    participant Sub as Strategy Sub-Modal
    participant API as Auth API
    participant Store as Credential Store

    User->>UI: Open MFA page
    UI->>Modal: render strategy selection
    User->>Modal: choose strategy (setChoose)
    Modal->>Sub: mount chosen sub-modal
    alt TOTP selected
        Sub->>Sub: generate TOTP secret & otpauth URI (otpauth)
        Sub->>Sub: render QR (qrcode -> data URL)
        Sub->>User: display QR
    else Email or Phone
        Sub->>API: POST /mfa/verify { userId, strategy }
        API-->>Sub: verification pending/ok
    end
    User->>Modal: submit code
    Modal->>API: POST /mfa { userId, strategy, code, secret }
    API-->>Modal: returns mfaId
    Modal->>Store: update credential (MFA configured)
    Store-->>UI: reflect new auth state
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Poem

🐰 Hoppy Refactor Note
Tailwind stitches, tabs put to rest,
MFA modals bustle, each one dressed,
QR hops out, email and phone too,
Auth paths now point to PUBLIC view,
A rabbit’s cheer — fresh UI, anew! 🥕✨

🚥 Pre-merge checks | ✅ 2 | ❌ 3

❌ Failed checks (2 warnings, 1 inconclusive)

Check name Status Explanation Resolution
Description check ⚠️ Warning The pull request description is entirely missing; no content was provided by the author beyond the template structure. Add a comprehensive description explaining the motivation, changes made, and any testing performed. Include links to related issues or design documents as applicable.
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title 'Feature/renew style' is vague and does not clearly convey the main changes; it uses generic terminology without describing what specific styling or features are being updated. Replace with a more descriptive title that captures the main change, such as 'Refactor styling to Tailwind CSS and update UI components' or 'Add Tailwind CSS styling and modernize component layouts'.
✅ Passed checks (2 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/renew-style

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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 12

Note

Due to the large number of review comments, Critical, Major severity comments were prioritized as inline comments.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/routes/login/default.svelte (1)

1-91: ⚠️ Potential issue | 🟡 Minor

Prettier check failing on this file.

CI reports a Prettier formatting warning for this file. Please run the project formatter (npm run format or equivalent) before merging so the pipeline goes green.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/routes/login/default.svelte` around lines 1 - 91, Prettier is complaining
about formatting in this component; run the project formatter (e.g., npm run
format or npx prettier --write "src/routes/login/default.svelte") to reformat
the file (including the <script> block and JSX-like markup around the submit
function, email/password bindings, and conditional loginError block), then stage
and commit the formatted changes so CI passes; if your repo uses a specific
Prettier config, ensure it’s applied when running the formatter.
🟡 Minor comments (13)
src/routes/invoices/+page.svelte-7-7 (1)

7-7: ⚠️ Potential issue | 🟡 Minor

Promote the page heading to <h1>.

This is the primary page title for the Invoices route ("Invoice Ledger"), but it's marked as <h2>. The page has no <h1> element—neither in the layout nor in the Menu component. Every route page should have exactly one <h1> for accessibility and SEO.

Suggested change
-    <h2 class="mb-6 text-4xl uppercase tracking-tight text-stone-800">Invoice Ledger</h2>
+    <h1 class="mb-6 text-4xl uppercase tracking-tight text-stone-800">Invoice Ledger</h1>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/routes/invoices/`+page.svelte at line 7, Change the top-level heading in
the Invoices page from an <h2> to an <h1> so the route has a single primary page
title; locate the element in src/routes/invoices/+page.svelte (the line
rendering "Invoice Ledger" currently as <h2 class="mb-6 text-4xl uppercase
tracking-tight text-stone-800">) and replace the tag with <h1> while preserving
the existing classes and text content so accessibility and SEO requirements are
satisfied.
tailwind.config.ts-1-11 (1)

1-11: ⚠️ Potential issue | 🟡 Minor

Replace require() with an ESM import (ESLint violation) and prefer satisfies over as.

The file uses require('@tailwindcss/typography') on line 10, which violates the @typescript-eslint/no-require-imports rule that is active in the project's ESLint configuration. Since the file is already an ESM/TS module with import type { Config }, switch to a top-level import for the plugin. Using satisfies Config instead of as Config is more idiomatic TypeScript and preserves literal type inference while still validating the configuration shape.

♻️ Proposed fix
 import type { Config } from 'tailwindcss'
+import typography from '@tailwindcss/typography'

 export default {
   content: ['./src/**/*.{html,js,svelte,ts}'],

   theme: {
     extend: {}
   },

-  plugins: [require('@tailwindcss/typography')]
-} as Config
+  plugins: [typography]
+} satisfies Config
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tailwind.config.ts` around lines 1 - 11, The config currently uses CommonJS
require and a type assertion: replace require('@tailwindcss/typography') with a
top-level ESM import (e.g., import typography from '@tailwindcss/typography')
and then reference that imported identifier in the plugins array (the plugins
variable in the exported object); also change the export from using "as Config"
to using "satisfies Config" to preserve literal types while validating shape
(update the default export object that contains content/theme/plugins to end
with "satisfies Config" instead of "as Config").
src/routes/mfa/_modals/email.svelte-13-13 (1)

13-13: ⚠️ Potential issue | 🟡 Minor

Typo: shoudlshould.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/routes/mfa/_modals/email.svelte` at line 13, Fix the typo in the thrown
Error message: locate the throw new Error('Credential shoudl be setted by now')
and change it to a correct, idiomatic message such as throw new
Error('Credential should be set by now') (or 'Credentials should be set by now')
so the error text is spelled and phrased correctly.
src/routes/login/+page.svelte-1-30 (1)

1-30: ⚠️ Potential issue | 🟡 Minor

Prettier check failing on this file.

CI is reporting a Prettier formatting warning here. Please run the formatter before merging.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/routes/login/`+page.svelte around lines 1 - 30, Prettier formatting is
failing in this file; run the project Prettier (or npm/yarn format script) on
src/routes/login/+page.svelte to reformat the script and template so it matches
the repo rules, then commit the changes; focus on normalizing
whitespace/indentation around the <script> block and template expressions
(symbols to inspect: mfaChoose, mfaCodeHash, setMfaChoose, setMfaCodeHash, and
the Choose/Code/Default component usages) to ensure CI passes.
src/routes/mfa/_modals/phone.svelte-13-15 (1)

13-15: ⚠️ Potential issue | 🟡 Minor

Fix typo in error message.

Change "shoudl" to "should" in the error string on line 13. The argument order to verifyMfa($credential.id, Strategy.PHONE, $credential.token) is correct and matches the function signature verifyMfa(userId: string, strategy: Strategy, token: string).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/routes/mfa/_modals/phone.svelte` around lines 13 - 15, The error string
contains a typo ("shoudl"); update the thrown message in the block that checks
$credential to read "Credential should be set by now" (or similar) so it's
spelled correctly; keep the subsequent call to verifyMfa($credential.id,
Strategy.PHONE, $credential.token) as-is since it matches the verifyMfa(userId:
string, strategy: Strategy, token: string) signature.
src/routes/+layout.svelte-25-32 (1)

25-32: ⚠️ Potential issue | 🟡 Minor

Inconsistent indentation triggering Prettier check.

CI reports a Prettier warning on this file. The <Menu /> and <main> lines use 4-space indentation while the surrounding markup uses 2 spaces. Running prettier --write should normalize it.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/routes/`+layout.svelte around lines 25 - 32, The JSX-like markup in
+layout.svelte has inconsistent indentation: <Menu /> and <main class="flex-grow
bg-white"> are indented with 4 spaces while the surrounding <div> uses 2; run
prettier --write on src/routes/+layout.svelte (or manually adjust indentation to
2 spaces for the <Menu /> and <main> lines and their child block containing
{`@render` children?.()}) to normalize formatting and satisfy the Prettier check.
src/routes/+layout.svelte-16-20 (1)

16-20: ⚠️ Potential issue | 🟡 Minor

Unawaited credential.logout(token) on a token that just failed to refresh.

A few concerns in the catch branch:

  1. credential.logout(token) returns a Promise that isn’t awaited; if it rejects (highly likely given the token just failed refresh, the server will probably return a non-200 from /logout), it becomes an unhandled promise rejection.
  2. Calling logout with a token already known to be invalid is of dubious value — the server will reject it, and user.set(null) (which logout does on success) won’t run, leaving any stale $credential in memory.

Consider either dropping the credential.logout(token) call here (the sessionStorage.removeItem plus a manual user.set(null) is sufficient) or at minimum wrapping it in .catch(() => {}).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/routes/`+layout.svelte around lines 16 - 20, The catch block currently
calls credential.logout(token) without awaiting it and with an invalid token,
causing potential unhandled rejections and leaving stale state; update the catch
branch to either remove the logout call and explicitly clear client state (call
user.set(null) after sessionStorage.removeItem('token')) or, if keeping logout,
await credential.logout(token) and wrap it in try/catch (e.g., await
credential.logout(token).catch(()=>{})) to swallow errors and still call
user.set(null) to ensure local state is cleared; reference credential.logout,
sessionStorage.removeItem, and user.set to locate the change.
src/routes/login/code.svelte-41-43 (1)

41-43: ⚠️ Potential issue | 🟡 Minor

“Resend Code” button is a no-op.

The button has no onclick handler, so clicking it does nothing. Either wire it to a resend action or remove it until implemented to avoid misleading users.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/routes/login/code.svelte` around lines 41 - 43, The "Resend Code" button
in src/routes/login/code.svelte is a no-op because it lacks an onclick handler;
either wire the button (the element with text "Resend Code") to the resend
action (e.g., call an existing or new function named resendCode or handleResend
that triggers the resend flow and handles success/error states) or remove the
button until that function is implemented so users aren't misled.
src/routes/mfa/+page.svelte-15-20 (1)

15-20: ⚠️ Potential issue | 🟡 Minor

Typo in error message: “shoudl” → “should”.

✏️ Proposed fix
-      throw new Error('Credential shoudl be setted by now')
+      throw new Error('Credential should be set by now')
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/routes/mfa/`+page.svelte around lines 15 - 20, Fix the typo in the thrown
Error string inside the onMount block: change "Credential shoudl be setted by
now" to "Credential should be set by now" (the code references onMount,
$credential and listMfa and assigns mfaList), so update the error message text
only to the corrected phrasing.
src/routes/Menu.svelte-6-11 (1)

6-11: ⚠️ Potential issue | 🟡 Minor

Catch logout errors before clearing local session.

If credential.logout($credential.token) throws (e.g., network failure or 4xx/5xx from /logout), the exception will bubble out of the click handler, sessionStorage.removeItem('token') will not run, and the in-memory $credential will not be cleared (the store sets user.set(null) only on success). The user appears “still logged in” after attempting to log out. Consider clearing client state in a finally block (or unconditionally) so the local session is always invalidated.

🛡️ Proposed fix
   async function logout() {
     if ($credential) {
-      await credential.logout($credential.token)
-      sessionStorage.removeItem('token')
+      try {
+        await credential.logout($credential.token)
+      } catch (e) {
+        console.error(e)
+      } finally {
+        sessionStorage.removeItem('token')
+      }
     }
   }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/routes/Menu.svelte` around lines 6 - 11, The logout flow in the logout()
function can throw from credential.logout($credential.token) and prevent
client-side cleanup; wrap the async call in try/catch/finally so you always
clear local session state: call credential.logout inside a try (optionally log
or handle errors in catch), and in finally unconditionally remove
sessionStorage.removeItem('token') and clear the in-memory credential/user store
(e.g., set the store to null or call the existing store-reset method) to ensure
the client appears logged out even if the network request fails.
src/routes/mfa/modal.svelte-22-30 (1)

22-30: ⚠️ Potential issue | 🟡 Minor

Typos in error messages and missing TOTP-secret guard.

  • "Credential shoudl be setted by now" → "Credential should be set by now".
  • "Mfa should be choosed by now" → "MFA should have been chosen by now".
  • For Strategy.GA, secret may still be null (e.g., if the TOTP toDataURL callback errored — see comment on totp.svelte). Consider guarding before calling createMfa, or making the submit button additionally check strategyChoosed !== Strategy.GA || secret !== null.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/routes/mfa/modal.svelte` around lines 22 - 30, Fix the typos and add a
guard for missing TOTP secret in createNewStrategy: correct the error strings
thrown when $credential or strategyChoosed are null to "Credential should be set
by now" and "MFA should have been chosen by now", and before calling createMfa
check that if strategyChoosed === Strategy.GA then secret !== null (otherwise
throw a clear error like "TOTP secret missing") or return early; alternatively
ensure the submit button only enables when strategyChoosed !== Strategy.GA ||
secret !== null so createMfa is never invoked with a null secret. Use the
function name createNewStrategy and variables $credential, strategyChoosed,
secret, createMfa and Strategy.GA to locate and apply the change.
src/routes/mfa/_modals/totp.svelte-30-35 (1)

30-35: ⚠️ Potential issue | 🟡 Minor

toDataURL errors are silently swallowed — UI gets stuck on "Generating...".

If toDataURL fails, imgUrl stays empty and setSecret is never called, so the parent's submit button remains usable while secret is null. The user has no feedback and the parent will POST secret: null for a TOTP enrollment. Consider surfacing the error and/or disabling submission until secret is populated:

       toDataURL(uri, (err, imageUrl) => {
-        if (!err) {
+        if (err) {
+          console.error('Failed to generate TOTP QR code', err)
+          return
+        }
           imgUrl = imageUrl
           setSecret(secret)
-        }
       })
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/routes/mfa/_modals/totp.svelte` around lines 30 - 35, The toDataURL
callback currently swallows errors so imgUrl remains empty and setSecret is
never called; update the callback for toDataURL to handle the error path
explicitly: if err, log or surface the error (set a local error state like
totpError) and still call setSecret(secret) or otherwise populate a safe
fallback so the parent knows a secret exists, and ensure the parent submit
button is disabled until secret is non-null (or totpError is cleared) so you
don't POST secret: null; reference the toDataURL callback, imgUrl, setSecret and
secret when making these changes and add a small UI message to show totpError if
present.
src/routes/mfa/strategy.svelte-32-44 (1)

32-44: ⚠️ Potential issue | 🟡 Minor

Strategy label inconsistency: bottom shows "TOTP", heading shows "GOOGLE_AUTHENTICATOR".

The overlay label maps Strategy.GA → "TOTP" (line 37), but the <h4> on line 44 prints the raw enum value ({strategy}), so users see "TOTP" on the card and "GOOGLE_AUTHENTICATOR" right below it. Reuse the same friendly label:

   <div class="mt-4 border-l-2 border-stone-400 pl-4">
-    <h4 class="text-xl font-bold uppercase tracking-widest text-stone-900">{strategy}</h4>
+    <h4 class="text-xl font-bold uppercase tracking-widest text-stone-900">
+      {strategy === Strategy.GA ? 'TOTP' : strategy}
+    </h4>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/routes/mfa/strategy.svelte` around lines 32 - 44, The heading prints the
raw enum (`{strategy}`) causing inconsistent labels (e.g., overlay uses "TOTP"
for Strategy.GA but the <h4> shows "GOOGLE_AUTHENTICATOR"); update the <h4> to
reuse the same friendly label logic used in the overlay by either calling a
shared helper (e.g., getStrategyLabel(strategy)) or using the same
conditional/lookup that maps Strategy.PHONE→"PHONE", Strategy.EMAIL→"EMAIL",
Strategy.GA→"TOTP", so the displayed heading and the overlay label are
identical.
🧹 Nitpick comments (18)
src/app.css (1)

98-108: clip property is deprecated — use clip-path for the visually-hidden helper.

Stylelint flags clip: rect(0 0 0 0) (line 100) as deprecated. The modern, accessibility-recommended pattern uses clip-path: inset(50%) (with clip kept only as a fallback if you need to support very old browsers).

♻️ Proposed fix
 .visually-hidden {
   border: 0;
-  clip: rect(0 0 0 0);
+  clip-path: inset(50%);
   height: auto;
   margin: 0;
   overflow: hidden;
   padding: 0;
   position: absolute;
   width: 1px;
   white-space: nowrap;
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/app.css` around lines 98 - 108, Update the .visually-hidden helper to use
the modern clip-path pattern: keep the existing clip: rect(0 0 0 0) as a
deprecated fallback but add clip-path: inset(50%) to replace clip for modern
browsers; ensure the rest of the rules on .visually-hidden (position, width,
height, overflow, white-space, etc.) remain unchanged so accessibility/visual
behavior is preserved. Refer to the .visually-hidden selector in the diff when
making the change.
src/routes/login/default.svelte (1)

22-26: Optional: clean up error message grammar.

'credential should be setted' reads awkwardly. Since this surfaces via loginError to users on the catch path (Line 29), consider rephrasing.

✏️ Suggested wording
-          throw new Error('credential should be setted')
+          throw new Error('Credential should be set by now')
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/routes/login/default.svelte` around lines 22 - 26, The thrown Error
message "credential should be setted" is grammatically awkward and surfaces to
users via the loginError path; update the message in the guard that checks
$credential (the throw in the block that currently uses
sessionStorage.setItem('token', $credential.token) and then goto(resolve('/')))
to a clearer phrase such as "Credential must be set" or "Credential is required"
so users see a properly worded error.
src/routes/users/table.svelte (1)

8-13: Optional: prefer redirect over throwing on missing credential.

If $credential is null on mount, throwing surfaces as an unhandled promise rejection rather than guiding the user to log in. Since this is consistent with src/routes/invoices/table.svelte, you could address both in a follow-up by returning early and goto('/login') instead.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/routes/users/table.svelte` around lines 8 - 13, Replace the throw in the
onMount block with a redirect: if $credential is falsy, call goto('/login') and
return early instead of throwing; ensure onMount still calls
listUser($credential.token) only when credential exists. Also add (or confirm)
the import of goto from '$app/navigation' and apply the same pattern to the
invoices table component (the analogous onMount). This keeps onMount from
causing an unhandled rejection and routes unauthenticated users to the login
page.
src/routes/users/+page.svelte (1)

7-9: Rename toogleModal to toggleModal.

All occurrences (7 total) are in src/routes/users/+page.svelte and src/routes/users/create.svelte. Update the function definition and all usages in both files to maintain consistency.

✏️ Proposed changes

In src/routes/users/+page.svelte:

-  function toogleModal() {
+  function toggleModal() {
     isModalOpen = !isModalOpen
   }
   
   <button
-    onclick={toogleModal}
+    onclick={toggleModal}
   >
   
   {`#if` isModalOpen}
-    <Create {toogleModal} />
+    <Create {toggleModal} />
   {/if}

In src/routes/users/create.svelte, update the prop definition and all usages similarly.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/routes/users/`+page.svelte around lines 7 - 9, Rename the misspelled
function toogleModal to toggleModal in its definition and update every call and
prop usage that references toogleModal (including the prop passed into the
create component) so names match; specifically change the function name and any
occurrences (calls, event handlers, and prop names) to toggleModal in both the
component that defines it and the component that receives it.
src/routes/Menu.svelte (2)

47-47: Use Svelte 5 onclick event syntax.

on:click is the legacy Svelte 4 event-directive syntax and is deprecated in Svelte 5. The rest of the PR (e.g., create.svelte line 68 onclick={toogleModal}) uses the new lowercase attribute form; align this for consistency.

♻️ Proposed change
-        on:click={logout}
+        onclick={logout}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/routes/Menu.svelte` at line 47, The Menu.svelte file uses the old Svelte
4 event directive "on:click={logout}" which is deprecated; replace it with the
Svelte 5 lowercase attribute form "onclick={logout}" where the logout handler is
attached (search for the logout identifier in Menu.svelte) so it matches the
rest of the codebase (e.g., create.svelte's onclick usage) and ensures
consistent event syntax.

13-26: Consider migrating to Svelte 5 runes for consistency with other components in this PR.

Other components in this refactor (choose.svelte, code.svelte, create.svelte, and mfa/+page.svelte) use Svelte 5 runes ($state, $props, $derived), but this file remains in legacy mode with the reactive declaration $: navItems = .... While $: syntax remains valid and supported in Svelte 5 legacy mode, migrating to $derived would align this component with the modernization approach taken across the PR.

♻️ Proposed refactor
-  $: navItems = [
-    { label: 'HOME', path: '/' },
-    ...($credential
-      ? [
-          { label: 'INVOICES', path: '/invoices' },
-          { label: 'MFA', path: '/mfa' },
-          { label: 'USERS', path: '/users' }
-        ]
-      : [])
-  ] as Routes[]
+  const navItems = $derived<Routes[]>([
+    { label: 'HOME', path: '/' },
+    ...($credential
+      ? [
+          { label: 'INVOICES', path: '/invoices' },
+          { label: 'MFA', path: '/mfa' },
+          { label: 'USERS', path: '/users' }
+        ]
+      : [])
+  ])
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/routes/Menu.svelte` around lines 13 - 26, Replace the legacy reactive
declaration for navItems with a Svelte 5 rune by converting the "$: navItems =
..." logic into a $derived store: create a $derived call that depends on
$credential and computes the same array (using the same Routes type and
label/path entries), export or reference the derived value as navItems so other
parts of the component use the new rune, and ensure the symbol names remain
navItems and $credential to keep compatibility with existing uses.
src/routes/+page.svelte (2)

9-14: Nit: rename type to singular Exhibit.

The type describes a single exhibit, not a collection, so Exhibits reads awkwardly with Exhibits[] (a collection of "exhibits-collections"). Renaming to Exhibit and using Exhibit[] is more idiomatic.

♻️ Proposed change
-  type Exhibits = {
+  type Exhibit = {
     name: string
     img: string
     stack: string[]
     url: string
   }
@@
-  ] satisfies Exhibits[]
+  ] satisfies Exhibit[]
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/routes/`+page.svelte around lines 9 - 14, Rename the type alias Exhibits
to the singular Exhibit and update all usages to Exhibit (and collections to
Exhibit[]); specifically change the type declaration named Exhibits to Exhibit
and replace any references like Exhibits[] or variables typed as Exhibits with
Exhibit[] or Exhibit respectively (ensure any props, lets, or function
signatures in +page.svelte that reference Exhibits are updated to the new
Exhibit name).

99-101: Prefer tech over array index as the keyed expression.

tech values within a stack are unique strings, making them a more meaningful key than the index i and avoiding the typical pitfalls of index-based keys if the list ever becomes dynamic.

♻️ Proposed change
-                {`#each` exhibit.stack as tech, i (i)}
+                {`#each` exhibit.stack as tech (tech)}
                   <li class="after:ml-2 after:content-['•'] last:after:content-['']">{tech}</li>
                 {/each}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/routes/`+page.svelte around lines 99 - 101, The each block is using the
array index `i` as the keyed expression which is brittle; change the key to the
unique string value `tech` in the loop over `exhibit.stack` (the `{`#each`
exhibit.stack as tech, i (i)}` block) so the keyed expression becomes `tech`,
ensuring stable identity when items change.
src/routes/login/choose.svelte (1)

14-17: Add error handling around chooseStrategy.

If credential.chooseStrategy throws (network error or non-200 from /mfa/choose), the unhandled rejection will leave the user stuck on the strategy screen with no feedback. Consider a try/catch that surfaces an inline error message similar to what default.svelte does with loginError.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/routes/login/choose.svelte` around lines 14 - 17, Wrap the call to
credential.chooseStrategy inside submitChoose with a try/catch so thrown errors
(network or non-200 responses) are handled; on catch, set a local error state
(e.g., reuse loginError or create chooseError) and surface that message to the
user instead of leaving them stuck, and only call setMfaCodeHash when
chooseStrategy succeeds; reference submitChoose, credential.chooseStrategy, and
setMfaCodeHash when making the changes.
src/routes/login/code.svelte (1)

26-31: Consider stricter input validation for the verification code.

maxlength="6" only caps the length; it allows non-digits and shorter inputs to be submitted, which will then fail at the API. Adding inputmode="numeric", pattern="\d{6}", autocomplete="one-time-code", and required will improve UX (mobile numeric keypad, OTP autofill) and prevent obviously invalid submissions.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/routes/login/code.svelte` around lines 26 - 31, The <input
id="login-code" bind:value={code}> only uses maxlength and thus permits
non-digits and empty/short submissions; update the element to enforce a 6-digit
OTP by adding inputmode="numeric" (to show numeric keypad), pattern="\d{6}" (to
validate exactly six digits), autocomplete="one-time-code" (enable OTP
autofill), and required (prevent empty submission); adjust any client-side
validation that reads {code} to rely on this stricter input pattern and ensure
the form won’t submit invalid values to the API.
src/routes/invoices/table.svelte (2)

8-13: Add error handling around the listInvoice call.

If listInvoice rejects (e.g., network error, non-200 from ${process.env.BILLING_URL}/invoice/...), the unhandled promise rejection in onMount will surface as an uncaught error and the table will silently stay empty. Consider wrapping in try/catch and surfacing a user-visible error state.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/routes/invoices/table.svelte` around lines 8 - 13, The onMount block
calls listInvoice($credential.id) without handling rejections, so any network or
non-200 error will cause an unhandled rejection and leave the table empty; wrap
the async call inside a try/catch in the onMount callback (keep the existing
credential guard for $credential), catch errors from listInvoice and set a
component-level error state (e.g., invoiceError) and/or a loading flag, and
surface that state in the UI so users see a friendly message instead of a silent
empty table; update code paths that consume list to handle the error/loading
states accordingly.

34-45: Nit: rename usrinvoice.

The iterated item is an invoice, not a user; renaming improves readability of the bound expressions ({usr.id}, {usr.user_id}, {usr.status}).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/routes/invoices/table.svelte` around lines 34 - 45, Rename the loop
variable from usr to invoice in the {`#each`} block so the bound expressions
reflect an invoice object: update the iterator signature ({`#each` list as invoice
(invoice.id)}) and all usages inside the row (replace usr.id, usr.user_id,
usr.status with invoice.id, invoice.user_id, invoice.status); ensure the key and
every reference in that block (including class/template bindings) are updated to
invoice to avoid stale identifiers.
src/routes/mfa/+page.svelte (1)

15-20: Add error handling for listMfa.

If listMfa($credential.id, $credential.token) rejects, the unhandled rejection in onMount leaves mfaList as null, so the page is permanently stuck on the “…waiting” loader with no feedback. Consider try/catch with a user-visible error state.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/routes/mfa/`+page.svelte around lines 15 - 20, The onMount block calling
listMfa can reject and leave mfaList null; wrap the await
listMfa($credential.id, $credential.token) call in a try/catch inside onMount,
catch the error and set a new error state (e.g., mfaError) and/or set mfaList to
an empty array so the UI can render an error message or empty state instead of
the perpetual loader; ensure you reference onMount, listMfa, mfaList and
$credential and update the component template to display mfaError to the user.
src/routes/users/create.svelte (2)

13-19: Add error handling and basic field validation.

If createNewUser rejects (e.g., duplicate email, weak password, network failure), the modal stays open with no feedback to the user, and the thrown error becomes an unhandled rejection. Consider try/catch with an inline error state, plus required/minlength attributes on the inputs (and autocomplete="new-password" on the password field).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/routes/users/create.svelte` around lines 13 - 19, The createNew function
currently throws/unhandled rejects and always calls toogleModal even on failure;
wrap the await createNewUser(...) call in a try/catch inside createNew, set a
local error state (e.g., createError) on catch to show user feedback, and only
call toogleModal() on success; also add basic client-side validation to the form
inputs (use required and minlength on name/email/password inputs and add
autocomplete="new-password" to the password field) so the browser prevents
invalid submissions before createNewUser is invoked. Ensure you reference
createNew, createNewUser, toogleModal and $credential when implementing the
changes.

22-24: Modal lacks keyboard/focus-trap accessibility.

The overlay is rendered as a plain <div> with no role="dialog", aria-modal="true", focus management, or Escape-to-close handling. Keyboard-only users can’t dismiss it, and screen readers won’t announce it as a dialog. Consider adding the ARIA attributes, returning focus to the trigger on close, and binding Escape to toogleModal().

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/routes/users/create.svelte` around lines 22 - 24, The modal overlay
currently lacks ARIA and keyboard/focus handling; update the modal container
<div> (the element you added with classes "fixed inset-0 ...") to include
role="dialog" and aria-modal="true", make the dialog element focusable (e.g.,
tabindex="-1") and programmatically move focus into it when opened and back to
the trigger when closed, implement a focus trap inside the dialog (keep focus
cycling within the modal), and add a keydown handler that listens for Escape and
calls the existing toogleModal() function to close; ensure all focus management
is tied to the lifecycle of the modal (open/close) so screen readers announce it
and keyboard users can dismiss and navigate it.
src/routes/mfa/modal.svelte (1)

47-53: Mixed == and === for the strategy comparison.

Line 47 uses === while lines 49 and 51 use ==. Use strict equality consistently:

-      {:else if strategyChoosed == Strategy.EMAIL}
+      {:else if strategyChoosed === Strategy.EMAIL}
         <EmailModal />
-      {:else if strategyChoosed == Strategy.PHONE}
+      {:else if strategyChoosed === Strategy.PHONE}
         <PhoneModal />
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/routes/mfa/modal.svelte` around lines 47 - 53, The comparisons for
selecting the modal use mixed equality operators: strategyChoosed ===
Strategy.GA but strategyChoosed == Strategy.EMAIL and strategyChoosed ==
Strategy.PHONE; update the latter two to use strict equality (===) so all checks
consistently compare strategyChoosed to Strategy.EMAIL and Strategy.PHONE with
=== in the conditional block that renders TotpModal, EmailModal, and PhoneModal.
src/routes/mfa/strategy.svelte (1)

14-18: getImage() has an implicit undefined return path.

If strategy ever falls outside the enum, getImage() returns undefined and the inline style becomes background-image: url(undefined). Add an explicit fallback (or return type) to make the intent clear:

   function getImage() {
     if (strategy === Strategy.PHONE) return imagePhoneUrl
     if (strategy === Strategy.EMAIL) return imageEmailUrl
-    if (strategy === Strategy.GA) return imageClockUrl
+    return imageClockUrl
   }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/routes/mfa/strategy.svelte` around lines 14 - 18, getImage() currently
returns undefined if strategy doesn't match Strategy.PHONE/EMAIL/GA; update the
function to always return a string by adding an explicit fallback return (e.g.,
a default image URL or empty string) so the inline style never becomes
background-image: url(undefined). Locate getImage and the Strategy enum usage
and ensure the final return covers cases outside Strategy.PHONE, Strategy.EMAIL,
and Strategy.GA, referencing imagePhoneUrl, imageEmailUrl, and imageClockUrl as
applicable.
package.json (1)

54-58: Remove unused otplib dependency — only otpauth is used for TOTP.

otpauth was added for the new TOTP modal, but otplib is no longer referenced anywhere in the codebase. Removing it reduces bundle size and supply-chain surface.

   "dependencies": {
     "otpauth": "^9.5.1",
-    "otplib": "^12.0.1",
     "qrcode": "^1.5.4"
   }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@package.json` around lines 54 - 58, The package.json lists "otplib" in
dependencies but the codebase only uses "otpauth" for TOTP; remove the "otplib"
entry from the "dependencies" section (the string "otplib" is the unique symbol
to remove) and then regenerate the lockfile by running your package manager
install (npm install or yarn install) so the lockfile and node_modules are
updated; ensure CI passes and no code references to otplib remain before
merging.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@e2e/Header.test.ts`:
- Line 7: The test assertion in Header.test.ts uses a stale expectation
expecting navElement toContainText('Auth +'); update the assertion to match the
current navigation UI provided by Menu.svelte (e.g., check for one of the actual
items like 'HOME' or 'LOGIN') — locate the navElement in the test and replace
the stale toContainText('Auth +') with a check that matches current labels (for
example toContainText('HOME') or a regex/multiple assertions that verify 'HOME'
and/or 'LOGIN') so the E2E test reflects the updated Menu.svelte output.

In `@src/routes/invoices/table.svelte`:
- Around line 27-32: Remove the leftover hardcoded demo table row (the <tr
class="group transition-colors hover:bg-stone-50"> containing the sample IDs
"824ebdd6-7ee9-46b7-b3f8-1ca41a39fd55", "a77aa649-5cdc-4d74-a695-6d2917c32619"
and status "pending") so it no longer renders; the table body should be
populated only by the existing {`#each` list} (or listInvoice) loop—delete that
static <tr> block to avoid duplicate/fake invoices and rely solely on the
dynamic rendering logic.

In `@src/routes/login/choose.svelte`:
- Around line 14-22: The submit handler submitChoose doesn't prevent the
browser's default form submission, so the page reloads before
credential.chooseStrategy and setMfaCodeHash complete; update submitChoose to
accept the submit event (e.g., function submitChoose(event)) and call
event.preventDefault() at the top, then perform the await
credential.chooseStrategy(hash, strategyOption as Strategy) and call
setMfaCodeHash(mfaCodeHash); ensure the form still uses onsubmit={submitChoose}
(or alternatively use Svelte's preventDefault modifier) so the MFA flow advances
correctly.

In `@src/routes/login/code.svelte`:
- Around line 11-18: The form's onsubmit={submitCode} handler doesn't prevent
the browser's default submit, causing a full page reload; update the async
submitCode function to accept the event parameter, call event.preventDefault()
at the top, then proceed to await credential.loginCode(hash, code) (ensure
onsubmit still references submitCode). This ensures the form won't perform a
native navigation on Enter or button click and allows credential.loginCode to
complete.

In `@src/routes/mfa/_modals/email.svelte`:
- Around line 5-9: The manual subscription to the Svelte store credential
(credential.subscribe(...)) leaks because its unsubscribe is never called;
remove the subscribe block and the local mutable user variable, and instead use
the auto-subscribed $credential reactive value wherever user is referenced in
this component (replace usages of user with $credential and ensure type
expectations as User | null are preserved).

In `@src/routes/mfa/_modals/totp.svelte`:
- Around line 11-17: The manual subscription credential.subscribe(...) in the
component body leaks because it isn't unsubscribed; replace this pattern by
using Svelte's auto-subscribed store accessor ($credential) and remove the
manual user $state local variable and the subscribe callback (or, if you must
keep an explicit subscription, ensure you call unsubscribe in onDestroy).
Concretely, stop using credential.subscribe(...) and read the current user via
$credential (or derive a reactive user from $credential), remove the manual user
= value assignment and its $state declaration, and keep the existing
Secret/secret and imgUrl logic unchanged.

In `@src/routes/mfa/mfa.ts`:
- Around line 53-73: The verifyMfa function has a copy-paste error and a likely
incorrect return type: change the thrown message in verifyMfa from "MFA creation
didn't work" to "MFA verification didn't work" and then confirm the actual
response shape from the backend /mfa/verify endpoint (used by verifyMfa) —
update the function's Promise return type from Promise<{ mfaId: string }> to the
real contract (e.g., { success: boolean, token?: string } or whatever the API
returns) and adjust callers email.svelte and phone.svelte (either consume the
new fields or ignore the response) so types align with the backend.

In `@src/routes/mfa/modal.svelte`:
- Line 57: Prevent the form from triggering a full-page navigation by wrapping
the onsubmit handler to call event.preventDefault() before invoking
createNewStrategy (replace the bare onsubmit={createNewStrategy} with an
explicit wrapper that receives the event), and inside createNewStrategy ensure
the async createMfa call is awaited with try/catch (or .catch()) so rejections
are handled; on successful createMfa call invoke closeModal() to dismiss the
modal; also fix the user-facing typo strings "shoudl be setted" → "should have
been set" and "Mfa should be choosed" → "MFA should have been chosen".

In `@src/routes/users/create.svelte`:
- Around line 13-27: The form handler createNew is a plain DOM onsubmit so the
browser will perform a native submit/reload before your async work finishes;
update createNew to accept the event and call event.preventDefault() at the top
(i.e., change to async function createNew(event) { event.preventDefault(); ...
}) so createNewUser(name, email, password, $credential.token) can complete and
toogleModal() will run; alternatively you can switch the form binding to a
Svelte submit modifier (on:submit|preventDefault) — locate createNew,
createNewUser and toogleModal to apply the change.

In `@src/stores/auth.ts`:
- Line 3: Refactor and CI fixes: add a .env.example containing
PUBLIC_AUTH_URL=http://localhost:3001 and update README.md with instructions to
copy .env.example to .env or set PUBLIC_AUTH_URL in CI; in code, replace legacy
process.env.AUTH_URL usages in src/routes/users/users.ts with the exported
PUBLIC_AUTH_URL import (match the import pattern used in src/stores/auth.ts) so
the module reads PUBLIC_AUTH_URL from '$env/static/public' instead of
process.env, and ensure your CI/build injects PUBLIC_AUTH_URL into the
environment at build time (or commit a project-wide example) while verifying no
secret values are being exposed as PUBLIC_.

In `@test/Header.svelte.test.ts`:
- Around line 4-11: Delete the entire test file for the removed component:
remove test/Header.svelte.test.ts which imports Header from
'../src/routes/Header.svelte' and contains the render(Header) /
screen.getByText('Auth +') assertions, since Header.svelte no longer exists and
coverage is now handled by Menu.svelte; ensure no other tests import Header or
reference Header.svelte to prevent "Failed to resolve import" CI failures.

In `@test/Menu.svelte.test.ts`:
- Around line 7-23: The test must be split into two cases to match Menu.svelte's
conditional rendering tied to the Svelte store $credential: add one test for the
unauthenticated state (ensure only "HOME" renders and the "Login" link is
present, not the logout button) and one for the authenticated state where you
seed the credential store (import the credential store, set it to a truthy mock
before calling render(Menu)) and then assert that navItems "INVOICES", "MFA",
"USERS" appear, and that the logout button (getByTestId('logout-button')) is
present; remove the obsolete toHaveClass('isDisabled') assertion since that
class no longer exists. Ensure you reset/cleanup the credential store between
tests.

---

Outside diff comments:
In `@src/routes/login/default.svelte`:
- Around line 1-91: Prettier is complaining about formatting in this component;
run the project formatter (e.g., npm run format or npx prettier --write
"src/routes/login/default.svelte") to reformat the file (including the <script>
block and JSX-like markup around the submit function, email/password bindings,
and conditional loginError block), then stage and commit the formatted changes
so CI passes; if your repo uses a specific Prettier config, ensure it’s applied
when running the formatter.

---

Minor comments:
In `@src/routes/`+layout.svelte:
- Around line 25-32: The JSX-like markup in +layout.svelte has inconsistent
indentation: <Menu /> and <main class="flex-grow bg-white"> are indented with 4
spaces while the surrounding <div> uses 2; run prettier --write on
src/routes/+layout.svelte (or manually adjust indentation to 2 spaces for the
<Menu /> and <main> lines and their child block containing {`@render`
children?.()}) to normalize formatting and satisfy the Prettier check.
- Around line 16-20: The catch block currently calls credential.logout(token)
without awaiting it and with an invalid token, causing potential unhandled
rejections and leaving stale state; update the catch branch to either remove the
logout call and explicitly clear client state (call user.set(null) after
sessionStorage.removeItem('token')) or, if keeping logout, await
credential.logout(token) and wrap it in try/catch (e.g., await
credential.logout(token).catch(()=>{})) to swallow errors and still call
user.set(null) to ensure local state is cleared; reference credential.logout,
sessionStorage.removeItem, and user.set to locate the change.

In `@src/routes/invoices/`+page.svelte:
- Line 7: Change the top-level heading in the Invoices page from an <h2> to an
<h1> so the route has a single primary page title; locate the element in
src/routes/invoices/+page.svelte (the line rendering "Invoice Ledger" currently
as <h2 class="mb-6 text-4xl uppercase tracking-tight text-stone-800">) and
replace the tag with <h1> while preserving the existing classes and text content
so accessibility and SEO requirements are satisfied.

In `@src/routes/login/`+page.svelte:
- Around line 1-30: Prettier formatting is failing in this file; run the project
Prettier (or npm/yarn format script) on src/routes/login/+page.svelte to
reformat the script and template so it matches the repo rules, then commit the
changes; focus on normalizing whitespace/indentation around the <script> block
and template expressions (symbols to inspect: mfaChoose, mfaCodeHash,
setMfaChoose, setMfaCodeHash, and the Choose/Code/Default component usages) to
ensure CI passes.

In `@src/routes/login/code.svelte`:
- Around line 41-43: The "Resend Code" button in src/routes/login/code.svelte is
a no-op because it lacks an onclick handler; either wire the button (the element
with text "Resend Code") to the resend action (e.g., call an existing or new
function named resendCode or handleResend that triggers the resend flow and
handles success/error states) or remove the button until that function is
implemented so users aren't misled.

In `@src/routes/Menu.svelte`:
- Around line 6-11: The logout flow in the logout() function can throw from
credential.logout($credential.token) and prevent client-side cleanup; wrap the
async call in try/catch/finally so you always clear local session state: call
credential.logout inside a try (optionally log or handle errors in catch), and
in finally unconditionally remove sessionStorage.removeItem('token') and clear
the in-memory credential/user store (e.g., set the store to null or call the
existing store-reset method) to ensure the client appears logged out even if the
network request fails.

In `@src/routes/mfa/_modals/email.svelte`:
- Line 13: Fix the typo in the thrown Error message: locate the throw new
Error('Credential shoudl be setted by now') and change it to a correct,
idiomatic message such as throw new Error('Credential should be set by now') (or
'Credentials should be set by now') so the error text is spelled and phrased
correctly.

In `@src/routes/mfa/_modals/phone.svelte`:
- Around line 13-15: The error string contains a typo ("shoudl"); update the
thrown message in the block that checks $credential to read "Credential should
be set by now" (or similar) so it's spelled correctly; keep the subsequent call
to verifyMfa($credential.id, Strategy.PHONE, $credential.token) as-is since it
matches the verifyMfa(userId: string, strategy: Strategy, token: string)
signature.

In `@src/routes/mfa/_modals/totp.svelte`:
- Around line 30-35: The toDataURL callback currently swallows errors so imgUrl
remains empty and setSecret is never called; update the callback for toDataURL
to handle the error path explicitly: if err, log or surface the error (set a
local error state like totpError) and still call setSecret(secret) or otherwise
populate a safe fallback so the parent knows a secret exists, and ensure the
parent submit button is disabled until secret is non-null (or totpError is
cleared) so you don't POST secret: null; reference the toDataURL callback,
imgUrl, setSecret and secret when making these changes and add a small UI
message to show totpError if present.

In `@src/routes/mfa/`+page.svelte:
- Around line 15-20: Fix the typo in the thrown Error string inside the onMount
block: change "Credential shoudl be setted by now" to "Credential should be set
by now" (the code references onMount, $credential and listMfa and assigns
mfaList), so update the error message text only to the corrected phrasing.

In `@src/routes/mfa/modal.svelte`:
- Around line 22-30: Fix the typos and add a guard for missing TOTP secret in
createNewStrategy: correct the error strings thrown when $credential or
strategyChoosed are null to "Credential should be set by now" and "MFA should
have been chosen by now", and before calling createMfa check that if
strategyChoosed === Strategy.GA then secret !== null (otherwise throw a clear
error like "TOTP secret missing") or return early; alternatively ensure the
submit button only enables when strategyChoosed !== Strategy.GA || secret !==
null so createMfa is never invoked with a null secret. Use the function name
createNewStrategy and variables $credential, strategyChoosed, secret, createMfa
and Strategy.GA to locate and apply the change.

In `@src/routes/mfa/strategy.svelte`:
- Around line 32-44: The heading prints the raw enum (`{strategy}`) causing
inconsistent labels (e.g., overlay uses "TOTP" for Strategy.GA but the <h4>
shows "GOOGLE_AUTHENTICATOR"); update the <h4> to reuse the same friendly label
logic used in the overlay by either calling a shared helper (e.g.,
getStrategyLabel(strategy)) or using the same conditional/lookup that maps
Strategy.PHONE→"PHONE", Strategy.EMAIL→"EMAIL", Strategy.GA→"TOTP", so the
displayed heading and the overlay label are identical.

In `@tailwind.config.ts`:
- Around line 1-11: The config currently uses CommonJS require and a type
assertion: replace require('@tailwindcss/typography') with a top-level ESM
import (e.g., import typography from '@tailwindcss/typography') and then
reference that imported identifier in the plugins array (the plugins variable in
the exported object); also change the export from using "as Config" to using
"satisfies Config" to preserve literal types while validating shape (update the
default export object that contains content/theme/plugins to end with "satisfies
Config" instead of "as Config").

---

Nitpick comments:
In `@package.json`:
- Around line 54-58: The package.json lists "otplib" in dependencies but the
codebase only uses "otpauth" for TOTP; remove the "otplib" entry from the
"dependencies" section (the string "otplib" is the unique symbol to remove) and
then regenerate the lockfile by running your package manager install (npm
install or yarn install) so the lockfile and node_modules are updated; ensure CI
passes and no code references to otplib remain before merging.

In `@src/app.css`:
- Around line 98-108: Update the .visually-hidden helper to use the modern
clip-path pattern: keep the existing clip: rect(0 0 0 0) as a deprecated
fallback but add clip-path: inset(50%) to replace clip for modern browsers;
ensure the rest of the rules on .visually-hidden (position, width, height,
overflow, white-space, etc.) remain unchanged so accessibility/visual behavior
is preserved. Refer to the .visually-hidden selector in the diff when making the
change.

In `@src/routes/`+page.svelte:
- Around line 9-14: Rename the type alias Exhibits to the singular Exhibit and
update all usages to Exhibit (and collections to Exhibit[]); specifically change
the type declaration named Exhibits to Exhibit and replace any references like
Exhibits[] or variables typed as Exhibits with Exhibit[] or Exhibit respectively
(ensure any props, lets, or function signatures in +page.svelte that reference
Exhibits are updated to the new Exhibit name).
- Around line 99-101: The each block is using the array index `i` as the keyed
expression which is brittle; change the key to the unique string value `tech` in
the loop over `exhibit.stack` (the `{`#each` exhibit.stack as tech, i (i)}` block)
so the keyed expression becomes `tech`, ensuring stable identity when items
change.

In `@src/routes/invoices/table.svelte`:
- Around line 8-13: The onMount block calls listInvoice($credential.id) without
handling rejections, so any network or non-200 error will cause an unhandled
rejection and leave the table empty; wrap the async call inside a try/catch in
the onMount callback (keep the existing credential guard for $credential), catch
errors from listInvoice and set a component-level error state (e.g.,
invoiceError) and/or a loading flag, and surface that state in the UI so users
see a friendly message instead of a silent empty table; update code paths that
consume list to handle the error/loading states accordingly.
- Around line 34-45: Rename the loop variable from usr to invoice in the {`#each`}
block so the bound expressions reflect an invoice object: update the iterator
signature ({`#each` list as invoice (invoice.id)}) and all usages inside the row
(replace usr.id, usr.user_id, usr.status with invoice.id, invoice.user_id,
invoice.status); ensure the key and every reference in that block (including
class/template bindings) are updated to invoice to avoid stale identifiers.

In `@src/routes/login/choose.svelte`:
- Around line 14-17: Wrap the call to credential.chooseStrategy inside
submitChoose with a try/catch so thrown errors (network or non-200 responses)
are handled; on catch, set a local error state (e.g., reuse loginError or create
chooseError) and surface that message to the user instead of leaving them stuck,
and only call setMfaCodeHash when chooseStrategy succeeds; reference
submitChoose, credential.chooseStrategy, and setMfaCodeHash when making the
changes.

In `@src/routes/login/code.svelte`:
- Around line 26-31: The <input id="login-code" bind:value={code}> only uses
maxlength and thus permits non-digits and empty/short submissions; update the
element to enforce a 6-digit OTP by adding inputmode="numeric" (to show numeric
keypad), pattern="\d{6}" (to validate exactly six digits),
autocomplete="one-time-code" (enable OTP autofill), and required (prevent empty
submission); adjust any client-side validation that reads {code} to rely on this
stricter input pattern and ensure the form won’t submit invalid values to the
API.

In `@src/routes/login/default.svelte`:
- Around line 22-26: The thrown Error message "credential should be setted" is
grammatically awkward and surfaces to users via the loginError path; update the
message in the guard that checks $credential (the throw in the block that
currently uses sessionStorage.setItem('token', $credential.token) and then
goto(resolve('/'))) to a clearer phrase such as "Credential must be set" or
"Credential is required" so users see a properly worded error.

In `@src/routes/Menu.svelte`:
- Line 47: The Menu.svelte file uses the old Svelte 4 event directive
"on:click={logout}" which is deprecated; replace it with the Svelte 5 lowercase
attribute form "onclick={logout}" where the logout handler is attached (search
for the logout identifier in Menu.svelte) so it matches the rest of the codebase
(e.g., create.svelte's onclick usage) and ensures consistent event syntax.
- Around line 13-26: Replace the legacy reactive declaration for navItems with a
Svelte 5 rune by converting the "$: navItems = ..." logic into a $derived store:
create a $derived call that depends on $credential and computes the same array
(using the same Routes type and label/path entries), export or reference the
derived value as navItems so other parts of the component use the new rune, and
ensure the symbol names remain navItems and $credential to keep compatibility
with existing uses.

In `@src/routes/mfa/`+page.svelte:
- Around line 15-20: The onMount block calling listMfa can reject and leave
mfaList null; wrap the await listMfa($credential.id, $credential.token) call in
a try/catch inside onMount, catch the error and set a new error state (e.g.,
mfaError) and/or set mfaList to an empty array so the UI can render an error
message or empty state instead of the perpetual loader; ensure you reference
onMount, listMfa, mfaList and $credential and update the component template to
display mfaError to the user.

In `@src/routes/mfa/modal.svelte`:
- Around line 47-53: The comparisons for selecting the modal use mixed equality
operators: strategyChoosed === Strategy.GA but strategyChoosed == Strategy.EMAIL
and strategyChoosed == Strategy.PHONE; update the latter two to use strict
equality (===) so all checks consistently compare strategyChoosed to
Strategy.EMAIL and Strategy.PHONE with === in the conditional block that renders
TotpModal, EmailModal, and PhoneModal.

In `@src/routes/mfa/strategy.svelte`:
- Around line 14-18: getImage() currently returns undefined if strategy doesn't
match Strategy.PHONE/EMAIL/GA; update the function to always return a string by
adding an explicit fallback return (e.g., a default image URL or empty string)
so the inline style never becomes background-image: url(undefined). Locate
getImage and the Strategy enum usage and ensure the final return covers cases
outside Strategy.PHONE, Strategy.EMAIL, and Strategy.GA, referencing
imagePhoneUrl, imageEmailUrl, and imageClockUrl as applicable.

In `@src/routes/users/`+page.svelte:
- Around line 7-9: Rename the misspelled function toogleModal to toggleModal in
its definition and update every call and prop usage that references toogleModal
(including the prop passed into the create component) so names match;
specifically change the function name and any occurrences (calls, event
handlers, and prop names) to toggleModal in both the component that defines it
and the component that receives it.

In `@src/routes/users/create.svelte`:
- Around line 13-19: The createNew function currently throws/unhandled rejects
and always calls toogleModal even on failure; wrap the await createNewUser(...)
call in a try/catch inside createNew, set a local error state (e.g.,
createError) on catch to show user feedback, and only call toogleModal() on
success; also add basic client-side validation to the form inputs (use required
and minlength on name/email/password inputs and add autocomplete="new-password"
to the password field) so the browser prevents invalid submissions before
createNewUser is invoked. Ensure you reference createNew, createNewUser,
toogleModal and $credential when implementing the changes.
- Around line 22-24: The modal overlay currently lacks ARIA and keyboard/focus
handling; update the modal container <div> (the element you added with classes
"fixed inset-0 ...") to include role="dialog" and aria-modal="true", make the
dialog element focusable (e.g., tabindex="-1") and programmatically move focus
into it when opened and back to the trigger when closed, implement a focus trap
inside the dialog (keep focus cycling within the modal), and add a keydown
handler that listens for Escape and calls the existing toogleModal() function to
close; ensure all focus management is tied to the lifecycle of the modal
(open/close) so screen readers announce it and keyboard users can dismiss and
navigate it.

In `@src/routes/users/table.svelte`:
- Around line 8-13: Replace the throw in the onMount block with a redirect: if
$credential is falsy, call goto('/login') and return early instead of throwing;
ensure onMount still calls listUser($credential.token) only when credential
exists. Also add (or confirm) the import of goto from '$app/navigation' and
apply the same pattern to the invoices table component (the analogous onMount).
This keeps onMount from causing an unhandled rejection and routes
unauthenticated users to the login page.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

Comment thread e2e/Header.test.ts Outdated
Comment on lines +27 to +32
<tr class="group transition-colors hover:bg-stone-50">
<td class="py-6 font-mono text-xs text-stone-400"
>824ebdd6-7ee9-46b7-b3f8-1ca41a39fd55</td>
<td class="py-6 text-stone-600">a77aa649-5cdc-4d74-a695-6d2917c32619</td>
<td class="py-6 italic text-stone-500 underline underline-offset-4">pending</td>
</tr>

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Remove leftover hardcoded sample row.

This static <tr> is a leftover demo row that will always render above the real list data, producing a duplicate/fake invoice on screen for every user. It should be deleted now that {#each list} populates the body from listInvoice.

🐛 Proposed fix
       <tbody class="divide-y divide-stone-100">
-        <tr class="group transition-colors hover:bg-stone-50">
-          <td class="py-6 font-mono text-xs text-stone-400"
-            >824ebdd6-7ee9-46b7-b3f8-1ca41a39fd55</td>
-          <td class="py-6 text-stone-600">a77aa649-5cdc-4d74-a695-6d2917c32619</td>
-          <td class="py-6 italic text-stone-500 underline underline-offset-4">pending</td>
-        </tr>
-
         {`#each` list as usr (usr.id)}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<tr class="group transition-colors hover:bg-stone-50">
<td class="py-6 font-mono text-xs text-stone-400"
>824ebdd6-7ee9-46b7-b3f8-1ca41a39fd55</td>
<td class="py-6 text-stone-600">a77aa649-5cdc-4d74-a695-6d2917c32619</td>
<td class="py-6 italic text-stone-500 underline underline-offset-4">pending</td>
</tr>
<tbody class="divide-y divide-stone-100">
{`#each` list as usr (usr.id)}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/routes/invoices/table.svelte` around lines 27 - 32, Remove the leftover
hardcoded demo table row (the <tr class="group transition-colors
hover:bg-stone-50"> containing the sample IDs
"824ebdd6-7ee9-46b7-b3f8-1ca41a39fd55", "a77aa649-5cdc-4d74-a695-6d2917c32619"
and status "pending") so it no longer renders; the table body should be
populated only by the existing {`#each` list} (or listInvoice) loop—delete that
static <tr> block to avoid duplicate/fake invoices and rely solely on the
dynamic rendering logic.

Comment thread src/routes/login/choose.svelte Outdated
Comment thread src/routes/login/code.svelte Outdated
Comment on lines +5 to +9
let user = $state<User | null>(null)

credential.subscribe((value: User | null) => {
user = value
})

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Manual store subscription leaks; use $credential directly.

credential.subscribe(...) returns an unsubscribe function that is never called, so the subscription persists across component destroy/remount. Since this file already uses the auto-subscribed $credential (Lines 12, 15), the local user state and the manual subscribe are redundant — $credential is reactive and reads as a User | null.

♻️ Proposed simplification
 <script lang="ts">
-  import { credential, type User } from '../../../stores/auth'
+  import { credential } from '../../../stores/auth'
   import { verifyMfa, Strategy } from '../mfa'
-
-  let user = $state<User | null>(null)
-
-  credential.subscribe((value: User | null) => {
-    user = value
-  })

   async function verifyNewStrategy() {
     if ($credential === null) {
       throw new Error('Credential should be set by now')
     }
     await verifyMfa($credential.id, Strategy.EMAIL, $credential.token)
   }
 </script>
@@
-      {`#if` user?.email}
-        Dispatch to <span class="font-mono font-bold not-italic text-stone-900">{user?.email}</span>
+      {`#if` $credential?.email}
+        Dispatch to <span class="font-mono font-bold not-italic text-stone-900">{$credential.email}</span>
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
let user = $state<User | null>(null)
credential.subscribe((value: User | null) => {
user = value
})
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/routes/mfa/_modals/email.svelte` around lines 5 - 9, The manual
subscription to the Svelte store credential (credential.subscribe(...)) leaks
because its unsubscribe is never called; remove the subscribe block and the
local mutable user variable, and instead use the auto-subscribed $credential
reactive value wherever user is referenced in this component (replace usages of
user with $credential and ensure type expectations as User | null are
preserved).

</main>

{#if !alreadySetup}
<form onsubmit={createNewStrategy} class="space-y-8">

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🌐 Web query:

Svelte 5 onsubmit preventDefault native event handler form

💡 Result:

In Svelte 5 (runes mode), use the new native DOM event syntax onsubmit={handleSubmit} on the

element, where handleSubmit receives the native SubmitEvent. To prevent the default form submission (page reload), explicitly call event.preventDefault inside the handler. Event modifiers like |preventDefault are removed. Example: <script> let name = $state(''); function handleSubmit(event) { event.preventDefault; console.log('Submitted:', name); // Handle form data, e.g., new FormData(event.currentTarget) } </script> Submit For SvelteKit form actions with progressive enhancement, prefer use:enhance (import from '$app/forms'), which handles preventDefault automatically: ... If implementing custom enhancement, use onsubmit={handleSubmit} with event.preventDefault and manual fetch to the action, then applyAction(deserialize(response)) as shown in SvelteKit docs. This matches standard DOM event handling (addEventListener('submit', ...)) with Svelte reactivity via runes ($state, bind:value).

Citations:


🏁 Script executed:

# First, let's check the file and read around line 57
head -n 100 src/routes/mfa/modal.svelte | tail -n +50

Repository: auth-plus/auth-plus-client

Length of output: 1669


🏁 Script executed:

# Also check what version of Svelte is being used
if [ -f package.json ]; then grep -i svelte package.json; fi

Repository: auth-plus/auth-plus-client

Length of output: 615


🏁 Script executed:

# Find the createNewStrategy function to understand its implementation
rg "createNewStrategy" src/routes/mfa/modal.svelte -A 15 -B 2

Repository: auth-plus/auth-plus-client

Length of output: 1479


Form submit will trigger a full page navigation in Svelte 5.

Svelte 5's native event syntax (onsubmit={handler}) does not auto-preventDefault() like the legacy on:submit|preventDefault directive did. As written, clicking "Confirm Activation" will submit the form to the current URL and reload the page, aborting the in-flight createMfa call. Wrap the handler to explicitly prevent default:

Suggested fix
-      <form onsubmit={createNewStrategy} class="space-y-8">
+      <form
+        onsubmit={(e) => {
+          e.preventDefault()
+          createNewStrategy()
+        }}
+        class="space-y-8">

Additionally:

  • Error handling: createMfa promise rejections are unhandled. Wrap in try/catch or add .catch().
  • Success path: Call closeModal() on success so the modal closes after MFA enrollment completes.
  • Typos: Line 22-24 has "shoudl be setted" → "should have been set" and "Mfa should be choosed" → "MFA should have been chosen".
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/routes/mfa/modal.svelte` at line 57, Prevent the form from triggering a
full-page navigation by wrapping the onsubmit handler to call
event.preventDefault() before invoking createNewStrategy (replace the bare
onsubmit={createNewStrategy} with an explicit wrapper that receives the event),
and inside createNewStrategy ensure the async createMfa call is awaited with
try/catch (or .catch()) so rejections are handled; on successful createMfa call
invoke closeModal() to dismiss the modal; also fix the user-facing typo strings
"shoudl be setted" → "should have been set" and "Mfa should be choosed" → "MFA
should have been chosen".

Comment thread src/routes/users/create.svelte Outdated
Comment thread src/stores/auth.ts
Comment thread test/Header.svelte.test.ts Outdated
Comment thread test/Menu.svelte.test.ts
Comment on lines +7 to +23
test('should render navigation menu with links and logout button', () => {
render(Menu)
expect(screen.getByText('HOME')).toBeInTheDocument()
expect(screen.getByText('HOME')).toHaveAttribute('href', '/')

expect(screen.getByText('INVOICES')).toBeInTheDocument()
expect(screen.getByText('INVOICES')).toHaveAttribute('href', '/invoices')
expect(screen.getByText('INVOICES')).toBeInTheDocument()
expect(screen.getByText('INVOICES')).toHaveAttribute('href', '/invoices')

expect(screen.getByText('MFA')).toBeInTheDocument()
expect(screen.getByText('MFA')).toHaveAttribute('href', '/mfa')
expect(screen.getByText('MFA')).toBeInTheDocument()
expect(screen.getByText('MFA')).toHaveAttribute('href', '/mfa')

expect(screen.getByText('USERS')).toBeInTheDocument()
expect(screen.getByText('USERS')).toHaveAttribute('href', '/users')
expect(screen.getByText('USERS')).toBeInTheDocument()
expect(screen.getByText('USERS')).toHaveAttribute('href', '/users')

expect(screen.getByTestId('logout-button')).toBeInTheDocument()
expect(screen.getByTestId('logout-button')).toHaveClass('isDisabled')
})
expect(screen.getByTestId('logout-button')).toBeInTheDocument()
expect(screen.getByTestId('logout-button')).toHaveClass('isDisabled')
})

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Test does not match the new Menu.svelte behavior — failing in CI.

After the refactor, navigation items and the logout button are conditional on $credential:

  • INVOICES, MFA, USERS are only added to navItems when $credential is truthy. The test never seeds the store, so only HOME renders and the assertion on line 12 fails (matches the CI error).
  • logout-button is rendered only inside {#if $credential != null}; in the unauthenticated state a Login link is shown instead.
  • toHaveClass('isDisabled') on line 22 references a class that does not exist anywhere in the new component markup, so this assertion would fail even if the button rendered.

Suggest splitting into two tests and seeding the credential store explicitly:

🐛 Proposed restructure
 import { render, screen } from '@testing-library/svelte'
-import { describe, test, expect } from 'vitest'
+import { describe, test, expect, beforeEach } from 'vitest'

 import Menu from '../src/routes/Menu.svelte'
+import { credential } from '../src/lib/stores/credential' // adjust to actual path

 describe('Menu.svelte', () => {
-  test('should render navigation menu with links and logout button', () => {
-    render(Menu)
-    expect(screen.getByText('HOME')).toBeInTheDocument()
-    expect(screen.getByText('HOME')).toHaveAttribute('href', '/')
-
-    expect(screen.getByText('INVOICES')).toBeInTheDocument()
-    expect(screen.getByText('INVOICES')).toHaveAttribute('href', '/invoices')
-
-    expect(screen.getByText('MFA')).toBeInTheDocument()
-    expect(screen.getByText('MFA')).toHaveAttribute('href', '/mfa')
-
-    expect(screen.getByText('USERS')).toBeInTheDocument()
-    expect(screen.getByText('USERS')).toHaveAttribute('href', '/users')
-
-    expect(screen.getByTestId('logout-button')).toBeInTheDocument()
-    expect(screen.getByTestId('logout-button')).toHaveClass('isDisabled')
-  })
+  beforeEach(() => credential.set(null))
+
+  test('renders only HOME and a Login link when unauthenticated', () => {
+    render(Menu)
+    expect(screen.getByText('HOME')).toHaveAttribute('href', '/')
+    expect(screen.queryByText('INVOICES')).not.toBeInTheDocument()
+    expect(screen.queryByTestId('logout-button')).not.toBeInTheDocument()
+    expect(screen.getByText('Login')).toHaveAttribute('href', '/login')
+  })
+
+  test('renders all nav links and the logout button when authenticated', () => {
+    credential.set({ id: '1', name: 'x', email: 'x@x', token: 't' } as never)
+    render(Menu)
+    expect(screen.getByText('INVOICES')).toHaveAttribute('href', '/invoices')
+    expect(screen.getByText('MFA')).toHaveAttribute('href', '/mfa')
+    expect(screen.getByText('USERS')).toHaveAttribute('href', '/users')
+    expect(screen.getByTestId('logout-button')).toBeInTheDocument()
+  })
 })
🧰 Tools
🪛 GitHub Actions: Continuous Integration on Pull Request

[error] 12-13: TestingLibraryElementError: Unable to find an element with the text "INVOICES" (expected by test(Menu.svelte)).

🪛 GitHub Check: Test

[failure] 12-12: test/Menu.svelte.test.ts > Menu.svelte > should render navigation menu with links and logout button
TestingLibraryElementError: Unable to find an element with the text: INVOICES. This could be because the text is broken up by multiple elements. In this case, you can provide a function for your text matcher to make your matcher more flexible.

Ignored nodes: comments, script, style

  • HOME
  •   </ul>
       
      <div
        class="flex items-center gap-4"
      >
        <a
          class="text-xs font-bold uppercase tracking-[0.2em] text-stone-500 no-underline transition-colors hover:text-stone-900"
          href="/login"
        >
          Login
        </a>
      </div>
    </nav>
    
❯ Object.getElementError node_modules/@testing-library/dom/dist/config.js:37:19 ❯ node_modules/@testing-library/dom/dist/query-helpers.js:76:38 ❯ node_modules/@testing-library/dom/dist/query-helpers.js:52:17 ❯ node_modules/@testing-library/dom/dist/query-helpers.js:95:19 ❯ test/Menu.svelte.test.ts:12:19
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/Menu.svelte.test.ts` around lines 7 - 23, The test must be split into
two cases to match Menu.svelte's conditional rendering tied to the Svelte store
$credential: add one test for the unauthenticated state (ensure only "HOME"
renders and the "Login" link is present, not the logout button) and one for the
authenticated state where you seed the credential store (import the credential
store, set it to a truthy mock before calling render(Menu)) and then assert that
navItems "INVOICES", "MFA", "USERS" appear, and that the logout button
(getByTestId('logout-button')) is present; remove the obsolete
toHaveClass('isDisabled') assertion since that class no longer exists. Ensure
you reset/cleanup the credential store between tests.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 7

🧹 Nitpick comments (5)
.github/workflows/continuous_integration.yml (1)

70-71: Consider sourcing PUBLIC_AUTH_URL from a GitHub Actions variable instead of hardcoding.

Hardcoding http://authentication.auth-plus.app in the workflow couples CI to a single environment value and makes it easy to drift from the actual deployment configuration. Since build:check only needs the variable to be defined for $env/static/public resolution, you can move it to a repository/organization variable (e.g., vars.PUBLIC_AUTH_URL) so changes don't require workflow edits. Also worth noting the value is http:// (not https://); even though it's only used at build-check time and never dereferenced, aligning it with the production scheme avoids confusion if someone later assumes this is a real endpoint.

♻️ Suggested change
       - name: Verify build
         run: npm run build:check
         env:
-          PUBLIC_AUTH_URL: http://authentication.auth-plus.app
+          PUBLIC_AUTH_URL: ${{ vars.PUBLIC_AUTH_URL || 'https://authentication.auth-plus.app' }}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/continuous_integration.yml around lines 70 - 71, The
workflow hardcodes PUBLIC_AUTH_URL to "http://authentication.auth-plus.app";
update the CI to read PUBLIC_AUTH_URL from a GitHub Actions
repository/organization variable instead of embedding the literal URL: replace
the literal env entry with an environment reference (e.g., use the GitHub
Actions vars/Secrets mechanism) so the build job that resolves
$env/static/public (the build:check job) reads the value from the repo/org
variable, and ensure the stored variable uses the intended scheme (https://) to
match production expectations; reference the PUBLIC_AUTH_URL env key and the
build:check job when making the change.
src/routes/users/create.svelte (1)

28-64: Inputs lack required and autocomplete attributes.

Because the inputs have no required (nor minlength/pattern), submitting an empty form will POST blank name/email/password to the API and rely on the server to reject. Adding native validation gives immediate feedback and prevents needless requests. Also consider autocomplete="new-password" on the password field (and appropriate values on name/email) so password managers behave correctly when admins create users.

♻️ Proposed diff (illustrative)
         <input
           id="create-user-name"
           bind:value={name}
           type="text"
+          required
+          autocomplete="name"
           placeholder="e.g. Jane Doe"
           class="w-full border-b border-stone-300 bg-transparent py-2 outline-none transition-colors focus:border-stone-900" />
       </div>
...
         <input
           id="create-user-email"
           bind:value={email}
           type="email"
+          required
+          autocomplete="email"
           placeholder="jane@example.com"
           class="w-full border-b border-stone-300 bg-transparent py-2 outline-none transition-colors focus:border-stone-900" />
...
         <input
           id="create-user-pw"
           bind:value={password}
           type="password"
+          required
+          autocomplete="new-password"
           placeholder="••••••••"
           class="w-full border-b border-stone-300 bg-transparent py-2 outline-none transition-colors focus:border-stone-900" />
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/routes/users/create.svelte` around lines 28 - 64, The form inputs (IDs
create-user-name, create-user-email, create-user-pw used in the form with
onsubmit={createNew}) need client-side validation and proper autocomplete hints:
add required to all three inputs, set autocomplete="name" for create-user-name,
autocomplete="email" for create-user-email, and autocomplete="new-password" for
create-user-pw; also add a sensible minlength (e.g., minlength="8") and/or a
password pattern attribute on the password input to enforce strength before
submission so empty/weak values are blocked by the browser.
e2e/Login.test.ts (2)

17-30: Minor: route glob also matches the page navigation /login.

**/login matches both the SvelteKit page route /login and the auth API endpoint. It's harmless here only because page.route(...) is registered after page.goto('/login') in beforeEach. If anyone later moves the route registration above the navigation, or if the app fetches the page again (prefetching, client navigations), the HTML response would be replaced with the JSON mock. Consider scoping to the auth host, e.g. **/${PUBLIC_AUTH_URL host}/login or matching on request.method() === 'POST' inside the handler.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@e2e/Login.test.ts` around lines 17 - 30, The route glob '**/login' in the
page.route handler can also intercept the page navigation for the SvelteKit
`/login` page; update the handler in Login.test.ts where page.route and
route.fulfill are used to narrow matching to only the auth API (either by
changing the glob to include the auth host/PORT from PUBLIC_AUTH_URL or by
checking request.method() === 'POST' inside the page.route callback) so only the
API POST to the auth endpoint is mocked and the HTML page navigation is not
replaced.

9-13: Optional: assert on stable test IDs rather than visual text.

toHaveText('Welcome Back') couples the e2e suite to copy that may be localized or rebranded. Adding data-testid attributes to the headings/labels in default.svelte and asserting on those would make this test resilient to copy changes.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@e2e/Login.test.ts` around lines 9 - 13, The test "should display the login
form correctly" couples assertions to visible copy; replace fragile text-based
assertions with stable test-id selectors: add data-testid attributes to the
login heading and labels in the component (e.g., default.svelte:
data-testid="login-heading", "login-email-label", "login-pw-label") and update
the test in Login.test.ts to assert on those locators
(page.locator('[data-testid="..."]')) instead of toHaveText('Welcome Back') so
the e2e check remains stable across localization/rebranding.
src/routes/+layout.svelte (1)

16-20: Minor: credential.logout(token) is fire-and-forget inside the catch.

If logout rejects (e.g., network error on revocation), the rejection is unhandled and only console.error(error) for the original refresh failure has been logged. Consider awaiting it (with its own try/catch) so revocation failures are at least observable, and so the user does not navigate before the token is revoked server-side.

♻️ Suggested change
       } catch (error) {
         console.error(error)
         sessionStorage.removeItem('token')
-        credential.logout(token)
+        try {
+          await credential.logout(token)
+        } catch (logoutError) {
+          console.error(logoutError)
+        }
       }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/routes/`+layout.svelte around lines 16 - 20, The catch block currently
calls credential.logout(token) fire-and-forget; change it to await the
revocation and handle its errors: inside the catch wrap await
credential.logout(token) in its own try/catch (or Promise.then/.catch) so any
rejection is logged via process/console and does not go unhandled, and only
after the awaited logout (or after handling its failure) perform
sessionStorage.removeItem('token') and any navigation—this ensures token
revocation is observed and avoids unhandled rejections for credential.logout.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@e2e/Login.test.ts`:
- Around line 36-40: The test reads sessionStorage too early and can race with
the async submit handler; modify the test so it waits for navigation before
reading storage: call expect(page).toHaveURL('/') (or otherwise await
navigation) immediately after triggering the submit (the existing
page.click('button[type="submit"]')) and only then call page.evaluate(() =>
sessionStorage.getItem('token')) to assign token and assert token ===
'mock-session-token'; update the code around the token variable and
page.evaluate usage in e2e/Login.test.ts accordingly.
- Around line 64-81: The route pattern **/api/login used in the test does not
match the actual auth request `${PUBLIC_AUTH_URL}/login` so the mock never
intercepts; update the route in the test (the handler in Login.test.ts that
calls page.route) to use the same pattern as the other tests (e.g. match
**/login or the exact host+path used by PUBLIC_AUTH_URL) so the request from the
auth client is intercepted and the 401 response is returned; ensure the
locator/assertions (errorMsg) remain unchanged.
- Around line 42-62: The mock response in the 'login with MFA triggers the MFA
Selection view' test uses incorrect strategy strings; update the route.fulfill
body to return strategyList matching the Strategy enum values (e.g.,
['EMAIL','GOOGLE_AUTHENTICATOR']) so the MFA UI renders correctly, and replace
the weak negative assertion await
expect(page.locator('form')).not.toContainText('Welcome Back') with a positive
assertion that checks for elements unique to choose.svelte such as await
expect(page.locator('text=Security Verification')).toBeVisible() or await
expect(page.locator('#login-strategy')).toBeVisible() to confirm the MFA
selection view appears.

In `@src/routes/`+layout.svelte:
- Around line 34-41: The global CSS rule :global(body) references font-family:
'Playfair Display', serif but the font isn't loaded; to fix, load Playfair
Display by adding an import to your global stylesheet (e.g., add `@import`
'@fontsource/playfair-display'; at the top of the stylesheet) or add a Google
Fonts link tag for Playfair Display in your HTML template's head; ensure the
import/link is present so the :global(body) rule actually uses Playfair Display
rather than falling back to serif.

In `@src/routes/login/default.svelte`:
- Around line 22-24: The error message thrown when $credential is missing uses
incorrect grammar ("should be setted"); update the throw in the block that
checks $credential (the if (!$credential) branch) to use proper phrasing such as
"credential should be set" so the thrown Error message is idiomatic and clear.

In `@src/routes/users/create.svelte`:
- Around line 23-25: Add proper dialog semantics and keyboard handling to the
modal container: give the outer modal div role="dialog" aria-modal="true" and
aria-labelledby pointing to the "New Identity" heading (add an id to the
heading, e.g., new-identity-title), wire an Escape key handler that calls the
existing toogleModal/toggleModal function to close the modal, and implement a
simple focus trap (capture initial focus when opening, constrain Tab/Shift+Tab
inside the modal and restore focus when closed) or replace the markup with a
native <dialog> for built-in trapping; ensure all interactive elements inside
the modal are reachable and that the heading id matches the aria-labelledby
value.
- Around line 13-20: The createNew handler currently calls createNewUser without
error handling or an in-flight guard; add a local reactive "submitting" flag and
an "errorMessage" string, then update createNew to return early if submitting is
true, set submitting = true before awaiting createNewUser, wrap the await in
try/catch to set errorMessage on failure (and keep the modal open), and only
call toogleModal() on success; ensure submitting is set back to false in a
finally block and wire the submit button's disabled attribute to submitting and
render errorMessage in the form for user feedback (referencing createNew,
createNewUser, toogleModal and $credential).

---

Nitpick comments:
In @.github/workflows/continuous_integration.yml:
- Around line 70-71: The workflow hardcodes PUBLIC_AUTH_URL to
"http://authentication.auth-plus.app"; update the CI to read PUBLIC_AUTH_URL
from a GitHub Actions repository/organization variable instead of embedding the
literal URL: replace the literal env entry with an environment reference (e.g.,
use the GitHub Actions vars/Secrets mechanism) so the build job that resolves
$env/static/public (the build:check job) reads the value from the repo/org
variable, and ensure the stored variable uses the intended scheme (https://) to
match production expectations; reference the PUBLIC_AUTH_URL env key and the
build:check job when making the change.

In `@e2e/Login.test.ts`:
- Around line 17-30: The route glob '**/login' in the page.route handler can
also intercept the page navigation for the SvelteKit `/login` page; update the
handler in Login.test.ts where page.route and route.fulfill are used to narrow
matching to only the auth API (either by changing the glob to include the auth
host/PORT from PUBLIC_AUTH_URL or by checking request.method() === 'POST' inside
the page.route callback) so only the API POST to the auth endpoint is mocked and
the HTML page navigation is not replaced.
- Around line 9-13: The test "should display the login form correctly" couples
assertions to visible copy; replace fragile text-based assertions with stable
test-id selectors: add data-testid attributes to the login heading and labels in
the component (e.g., default.svelte: data-testid="login-heading",
"login-email-label", "login-pw-label") and update the test in Login.test.ts to
assert on those locators (page.locator('[data-testid="..."]')) instead of
toHaveText('Welcome Back') so the e2e check remains stable across
localization/rebranding.

In `@src/routes/`+layout.svelte:
- Around line 16-20: The catch block currently calls credential.logout(token)
fire-and-forget; change it to await the revocation and handle its errors: inside
the catch wrap await credential.logout(token) in its own try/catch (or
Promise.then/.catch) so any rejection is logged via process/console and does not
go unhandled, and only after the awaited logout (or after handling its failure)
perform sessionStorage.removeItem('token') and any navigation—this ensures token
revocation is observed and avoids unhandled rejections for credential.logout.

In `@src/routes/users/create.svelte`:
- Around line 28-64: The form inputs (IDs create-user-name, create-user-email,
create-user-pw used in the form with onsubmit={createNew}) need client-side
validation and proper autocomplete hints: add required to all three inputs, set
autocomplete="name" for create-user-name, autocomplete="email" for
create-user-email, and autocomplete="new-password" for create-user-pw; also add
a sensible minlength (e.g., minlength="8") and/or a password pattern attribute
on the password input to enforce strength before submission so empty/weak values
are blocked by the browser.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 9f28e4aa-762f-4994-944f-c27c112446e4

📥 Commits

Reviewing files that changed from the base of the PR and between e483b78 and c1a734c.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (15)
  • .github/workflows/continuous_integration.yml
  • e2e/Header.test.ts
  • e2e/Login.test.ts
  • src/routes/+layout.svelte
  • src/routes/Menu.svelte
  • src/routes/login/+page.svelte
  • src/routes/login/choose.svelte
  • src/routes/login/code.svelte
  • src/routes/login/default.svelte
  • src/routes/mfa/modal.svelte
  • src/routes/users/create.svelte
  • src/routes/users/users.ts
  • tailwind.config.ts
  • test/Header.svelte.test.ts
  • test/Menu.svelte.test.ts
💤 Files with no reviewable changes (2)
  • e2e/Header.test.ts
  • test/Header.svelte.test.ts
✅ Files skipped from review due to trivial changes (2)
  • tailwind.config.ts
  • src/routes/login/+page.svelte
🚧 Files skipped from review as they are similar to previous changes (6)
  • test/Menu.svelte.test.ts
  • src/routes/users/users.ts
  • src/routes/Menu.svelte
  • src/routes/login/choose.svelte
  • src/routes/mfa/modal.svelte
  • src/routes/login/code.svelte

Comment thread e2e/Login.test.ts
Comment on lines +36 to +40
// Verify redirection and session storage
const token = await page.evaluate(() => sessionStorage.getItem('token'))
await expect(page).toHaveURL('/')
expect(token).toBe('mock-session-token')
})

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Race: read sessionStorage after asserting navigation, not before.

page.click('button[type="submit"]') resolves once the click is dispatched, not after submit() finishes its await credential.login(...) and goto(...). Reading sessionStorage immediately after the click can race with the async submit handler and may read null on a slow run. Wait for the URL change first, then read storage.

♻️ Proposed fix
-    // Verify redirection and session storage
-    const token = await page.evaluate(() => sessionStorage.getItem('token'))
-    await expect(page).toHaveURL('/')
-    expect(token).toBe('mock-session-token')
+    // Verify redirection first, then session storage (avoids reading before submit completes)
+    await expect(page).toHaveURL('/')
+    const token = await page.evaluate(() => sessionStorage.getItem('token'))
+    expect(token).toBe('mock-session-token')
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// Verify redirection and session storage
const token = await page.evaluate(() => sessionStorage.getItem('token'))
await expect(page).toHaveURL('/')
expect(token).toBe('mock-session-token')
})
// Verify redirection first, then session storage (avoids reading before submit completes)
await expect(page).toHaveURL('/')
const token = await page.evaluate(() => sessionStorage.getItem('token'))
expect(token).toBe('mock-session-token')
})
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@e2e/Login.test.ts` around lines 36 - 40, The test reads sessionStorage too
early and can race with the async submit handler; modify the test so it waits
for navigation before reading storage: call expect(page).toHaveURL('/') (or
otherwise await navigation) immediately after triggering the submit (the
existing page.click('button[type="submit"]')) and only then call
page.evaluate(() => sessionStorage.getItem('token')) to assign token and assert
token === 'mock-session-token'; update the code around the token variable and
page.evaluate usage in e2e/Login.test.ts accordingly.

Comment thread e2e/Login.test.ts
Comment on lines +42 to +62
test('login with MFA triggers the MFA Selection view', async ({ page }) => {
// Mock a response that returns an MFA hash and strategy list
await page.route('**/login', async (route) => {
await route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify({
hash: 'mfa-challenge-123',
strategyList: ['email', 'totp']
})
})
})

await page.fill('#login-email', 'mfa-user@company.com')
await page.fill('#login-pw', 'password123')
await page.click('button[type="submit"]')

// Verify that the 'Default' component is hidden and 'Choose' is shown
// Note: We check for elements that would exist in choose.svelte
await expect(page.locator('form')).not.toContainText('Welcome Back')
})

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Inspect Strategy type and choose.svelte text/testids to confirm a stronger positive assertion.
fd -t f 'mfa.ts' --exec cat {}
fd -t f 'choose.svelte' --exec cat {}

Repository: auth-plus/auth-plus-client

Length of output: 3796


Fix mock strategyList to match Strategy enum and strengthen the assertion.

Two issues:

  1. Strategy is an uppercase string enum (EMAIL, PHONE, GOOGLE_AUTHENTICATOR), but the mock uses lowercase strings ['email', 'totp'] that don't match enum values. Additionally, 'totp' doesn't exist in the enum — it should be 'GOOGLE_AUTHENTICATOR'. This mismatch will cause rendering to fail silently, and the test will pass for the wrong reason.

  2. expect(page.locator('form')).not.toContainText('Welcome Back') is a weak negative assertion. Instead, assert the presence of text or elements unique to choose.svelte, such as the "Security Verification" heading or the strategy selector with id login-strategy.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@e2e/Login.test.ts` around lines 42 - 62, The mock response in the 'login with
MFA triggers the MFA Selection view' test uses incorrect strategy strings;
update the route.fulfill body to return strategyList matching the Strategy enum
values (e.g., ['EMAIL','GOOGLE_AUTHENTICATOR']) so the MFA UI renders correctly,
and replace the weak negative assertion await
expect(page.locator('form')).not.toContainText('Welcome Back') with a positive
assertion that checks for elements unique to choose.svelte such as await
expect(page.locator('text=Security Verification')).toBeVisible() or await
expect(page.locator('#login-strategy')).toBeVisible() to confirm the MFA
selection view appears.

Comment thread e2e/Login.test.ts
Comment thread src/routes/+layout.svelte
Comment on lines +34 to 41
<style>
:global(body) {
margin: 0;
/* Updated global font to match the Gallery aesthetic */
font-family: 'Playfair Display', serif;
background-color: #f5f5f4; /* stone-100 */
}
</style>

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Look for any reference to Playfair Display loading (CSS `@import`, `@font-face`, <link>, or Tailwind config).
rg -nP --hidden -g '!**/node_modules/**' "Playfair[ _-]?Display|playfair"
fd -i 'app.css|app.html|tailwind.config' --exec cat {}

Repository: auth-plus/auth-plus-client

Length of output: 2814


Add Playfair Display font loading.

The global body rule uses font-family: 'Playfair Display', serif, but the font is not loaded. The codebase imports @fontsource/fira-mono in app.css but has no corresponding import for Playfair Display. Without loading the font, browsers will silently fall back to the generic serif family, defeating the intended Gallery aesthetic.

Add one of the following to app.css:

  • @import '@fontsource/playfair-display'; (if using fontsource)
  • Or link to Google Fonts in app.html: <link href="https://fonts.googleapis.com/css2?family=Playfair+Display&display=swap" rel="stylesheet">
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/routes/`+layout.svelte around lines 34 - 41, The global CSS rule
:global(body) references font-family: 'Playfair Display', serif but the font
isn't loaded; to fix, load Playfair Display by adding an import to your global
stylesheet (e.g., add `@import` '@fontsource/playfair-display'; at the top of the
stylesheet) or add a Google Fonts link tag for Playfair Display in your HTML
template's head; ensure the import/link is present so the :global(body) rule
actually uses Playfair Display rather than falling back to serif.

Comment on lines +22 to +24
if (!$credential) {
throw new Error('credential should be setted')
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Minor: grammatical typo in error message.

"should be setted" is not idiomatic English; use "should be set".

✏️ Proposed fix
-        if (!$credential) {
-          throw new Error('credential should be setted')
-        }
+        if (!$credential) {
+          throw new Error('credential should be set')
+        }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (!$credential) {
throw new Error('credential should be setted')
}
if (!$credential) {
throw new Error('credential should be set')
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/routes/login/default.svelte` around lines 22 - 24, The error message
thrown when $credential is missing uses incorrect grammar ("should be setted");
update the throw in the block that checks $credential (the if (!$credential)
branch) to use proper phrasing such as "credential should be set" so the thrown
Error message is idiomatic and clear.

Comment on lines +13 to +20
async function createNew(event: SubmitEvent) {
event.preventDefault()
if (!$credential) {
throw new Error('credential should be setted')
}
await createNewUser(name, email, password, $credential.token)
toogleModal()
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Add error handling and a submit-in-flight guard.

If createNewUser rejects (network error, 4xx/5xx), the exception propagates unhandled, the modal stays open with no user feedback, and any user can keep clicking "Create" to fire concurrent requests. Consider tracking an in-flight flag and surfacing the failure to the user.

♻️ Proposed refactor
   let name = $state('')
   let email = $state('')
   let password = $state('')
+  let submitting = $state(false)
+  let errorMessage = $state('')
 
   async function createNew(event: SubmitEvent) {
     event.preventDefault()
     if (!$credential) {
       throw new Error('credential should be setted')
     }
-    await createNewUser(name, email, password, $credential.token)
-    toogleModal()
+    if (submitting) return
+    submitting = true
+    errorMessage = ''
+    try {
+      await createNewUser(name, email, password, $credential.token)
+      toogleModal()
+    } catch (err) {
+      errorMessage = err instanceof Error ? err.message : 'Failed to create user'
+    } finally {
+      submitting = false
+    }
   }

You'll also want to disable the submit button while submitting is true and render errorMessage somewhere in the form.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
async function createNew(event: SubmitEvent) {
event.preventDefault()
if (!$credential) {
throw new Error('credential should be setted')
}
await createNewUser(name, email, password, $credential.token)
toogleModal()
}
let name = $state('')
let email = $state('')
let password = $state('')
let submitting = $state(false)
let errorMessage = $state('')
async function createNew(event: SubmitEvent) {
event.preventDefault()
if (!$credential) {
throw new Error('credential should be setted')
}
if (submitting) return
submitting = true
errorMessage = ''
try {
await createNewUser(name, email, password, $credential.token)
toogleModal()
} catch (err) {
errorMessage = err instanceof Error ? err.message : 'Failed to create user'
} finally {
submitting = false
}
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/routes/users/create.svelte` around lines 13 - 20, The createNew handler
currently calls createNewUser without error handling or an in-flight guard; add
a local reactive "submitting" flag and an "errorMessage" string, then update
createNew to return early if submitting is true, set submitting = true before
awaiting createNewUser, wrap the await in try/catch to set errorMessage on
failure (and keep the modal open), and only call toogleModal() on success;
ensure submitting is set back to false in a finally block and wire the submit
button's disabled attribute to submitting and render errorMessage in the form
for user feedback (referencing createNew, createNewUser, toogleModal and
$credential).

Comment on lines +23 to +25
<div
class="fixed inset-0 z-50 flex items-center justify-center bg-stone-900/80 p-4 backdrop-blur-sm">
<div class="w-full max-w-md border-t-4 border-stone-900 bg-stone-50 p-8 shadow-2xl">

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Modal isn't keyboard/screen-reader accessible.

The overlay is a plain <div> with no dialog semantics, no focus trap, and no Escape-to-close. Keyboard users can tab "behind" the modal into the underlying page, and assistive tech won't announce it as a dialog. At minimum add role="dialog", aria-modal="true", an aria-labelledby pointing to the "New Identity" heading, and an Escape handler that calls toogleModal. For full focus trapping, consider Svelte's native <dialog> element or a small focus-trap utility.

-<div
-  class="fixed inset-0 z-50 flex items-center justify-center bg-stone-900/80 p-4 backdrop-blur-sm">
-  <div class="w-full max-w-md border-t-4 border-stone-900 bg-stone-50 p-8 shadow-2xl">
-    <h2 class="mb-6 text-2xl uppercase tracking-widest text-stone-900">New Identity</h2>
+<svelte:window onkeydown={(e) => e.key === 'Escape' && toogleModal()} />
+<div
+  class="fixed inset-0 z-50 flex items-center justify-center bg-stone-900/80 p-4 backdrop-blur-sm">
+  <div
+    role="dialog"
+    aria-modal="true"
+    aria-labelledby="create-user-title"
+    class="w-full max-w-md border-t-4 border-stone-900 bg-stone-50 p-8 shadow-2xl">
+    <h2 id="create-user-title" class="mb-6 text-2xl uppercase tracking-widest text-stone-900">New Identity</h2>
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<div
class="fixed inset-0 z-50 flex items-center justify-center bg-stone-900/80 p-4 backdrop-blur-sm">
<div class="w-full max-w-md border-t-4 border-stone-900 bg-stone-50 p-8 shadow-2xl">
<svelte:window onkeydown={(e) => e.key === 'Escape' && toogleModal()} />
<div
class="fixed inset-0 z-50 flex items-center justify-center bg-stone-900/80 p-4 backdrop-blur-sm">
<div
role="dialog"
aria-modal="true"
aria-labelledby="create-user-title"
class="w-full max-w-md border-t-4 border-stone-900 bg-stone-50 p-8 shadow-2xl">
<h2 id="create-user-title" class="mb-6 text-2xl uppercase tracking-widest text-stone-900">New Identity</h2>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/routes/users/create.svelte` around lines 23 - 25, Add proper dialog
semantics and keyboard handling to the modal container: give the outer modal div
role="dialog" aria-modal="true" and aria-labelledby pointing to the "New
Identity" heading (add an id to the heading, e.g., new-identity-title), wire an
Escape key handler that calls the existing toogleModal/toggleModal function to
close the modal, and implement a simple focus trap (capture initial focus when
opening, constrain Tab/Shift+Tab inside the modal and restore focus when closed)
or replace the markup with a native <dialog> for built-in trapping; ensure all
interactive elements inside the modal are reachable and that the heading id
matches the aria-labelledby value.

@AndrewHanasiro
AndrewHanasiro merged commit 79cda32 into main Apr 27, 2026
6 of 7 checks passed
@AndrewHanasiro
AndrewHanasiro deleted the feature/renew-style branch April 27, 2026 02:01
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