Feature/renew style - #21
Conversation
|
Warning Rate limit exceeded
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 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. 📝 WalkthroughWalkthroughAdds 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
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
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 3❌ Failed checks (2 warnings, 1 inconclusive)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
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 | 🟡 MinorPrettier check failing on this file.
CI reports a Prettier formatting warning for this file. Please run the project formatter (
npm run formator 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 | 🟡 MinorPromote 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 | 🟡 MinorReplace
require()with an ESM import (ESLint violation) and prefersatisfiesoveras.The file uses
require('@tailwindcss/typography')on line 10, which violates the@typescript-eslint/no-require-importsrule that is active in the project's ESLint configuration. Since the file is already an ESM/TS module withimport type { Config }, switch to a top-levelimportfor the plugin. Usingsatisfies Configinstead ofas Configis 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 | 🟡 MinorTypo:
shoudl→should.🤖 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 | 🟡 MinorPrettier 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 | 🟡 MinorFix 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 signatureverifyMfa(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 | 🟡 MinorInconsistent 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. Runningprettier --writeshould 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 | 🟡 MinorUnawaited
credential.logout(token)on a token that just failed to refresh.A few concerns in the catch branch:
credential.logout(token)returns a Promise that isn’t awaited; if it rejects (highly likely given the token just failedrefresh, the server will probably return a non-200 from/logout), it becomes an unhandled promise rejection.- Calling
logoutwith a token already known to be invalid is of dubious value — the server will reject it, anduser.set(null)(whichlogoutdoes on success) won’t run, leaving any stale$credentialin memory.Consider either dropping the
credential.logout(token)call here (thesessionStorage.removeItemplus a manualuser.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
onclickhandler, 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 | 🟡 MinorTypo 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 | 🟡 MinorCatch 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$credentialwill not be cleared (the store setsuser.set(null)only on success). The user appears “still logged in” after attempting to log out. Consider clearing client state in afinallyblock (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 | 🟡 MinorTypos 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,secretmay still benull(e.g., if the TOTPtoDataURLcallback errored — see comment ontotp.svelte). Consider guarding before callingcreateMfa, or making the submit button additionally checkstrategyChoosed !== 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
toDataURLerrors are silently swallowed — UI gets stuck on "Generating...".If
toDataURLfails,imgUrlstays empty andsetSecretis never called, so the parent's submit button remains usable whilesecretisnull. The user has no feedback and the parent will POSTsecret: nullfor a TOTP enrollment. Consider surfacing the error and/or disabling submission untilsecretis 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 | 🟡 MinorStrategy 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:clipproperty is deprecated — useclip-pathfor the visually-hidden helper.Stylelint flags
clip: rect(0 0 0 0)(line 100) as deprecated. The modern, accessibility-recommended pattern usesclip-path: inset(50%)(withclipkept 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 vialoginErrorto 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
$credentialis null on mount, throwing surfaces as an unhandled promise rejection rather than guiding the user to log in. Since this is consistent withsrc/routes/invoices/table.svelte, you could address both in a follow-up by returning early andgoto('/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: RenametoogleModaltotoggleModal.All occurrences (7 total) are in
src/routes/users/+page.svelteandsrc/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 5onclickevent syntax.
on:clickis the legacy Svelte 4 event-directive syntax and is deprecated in Svelte 5. The rest of the PR (e.g.,create.svelteline 68onclick={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, andmfa/+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$derivedwould 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 singularExhibit.The type describes a single exhibit, not a collection, so
Exhibitsreads awkwardly withExhibits[](a collection of "exhibits-collections"). Renaming toExhibitand usingExhibit[]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: Prefertechover array index as the keyed expression.
techvalues within a stack are unique strings, making them a more meaningful key than the indexiand 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 aroundchooseStrategy.If
credential.chooseStrategythrows (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 whatdefault.sveltedoes withloginError.🤖 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. Addinginputmode="numeric",pattern="\d{6}",autocomplete="one-time-code", andrequiredwill 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 thelistInvoicecall.If
listInvoicerejects (e.g., network error, non-200 from${process.env.BILLING_URL}/invoice/...), the unhandled promise rejection inonMountwill 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: renameusr→invoice.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 forlistMfa.If
listMfa($credential.id, $credential.token)rejects, the unhandled rejection inonMountleavesmfaListasnull, 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
createNewUserrejects (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, plusrequired/minlengthattributes on the inputs (andautocomplete="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 norole="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 bindingEscapetotoogleModal().🤖 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 implicitundefinedreturn path.If
strategyever falls outside the enum,getImage()returnsundefinedand the inline style becomesbackground-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 unusedotplibdependency — onlyotpauthis used for TOTP.
otpauthwas added for the new TOTP modal, butotplibis 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
| <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> |
There was a problem hiding this comment.
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.
| <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.
| let user = $state<User | null>(null) | ||
|
|
||
| credential.subscribe((value: User | null) => { | ||
| user = value | ||
| }) |
There was a problem hiding this comment.
🛠️ 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.
| 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"> |
There was a problem hiding this comment.
🧩 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:
- 1: https://svelte-5-preview.vercel.app/docs/event-handlers
- 2: https://svelte.dev/docs/svelte/legacy-on
- 3: https://svelte.dev/docs/kit/form-actions
- 4: https://leyaa.ai/codefly/learn/svelte/qna/how-to-handle-form-submit-in-svelte
- 5: https://svelte.dev/tutorial/svelte/inline-handlers
- 6: https://stackoverflow.com/questions/79195227/svelte-5-form-data-not-reactive-in-submit-handler
- 7: https://kit.svelte.dev/docs/form-actions
🏁 Script executed:
# First, let's check the file and read around line 57
head -n 100 src/routes/mfa/modal.svelte | tail -n +50Repository: 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; fiRepository: 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 2Repository: 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:
createMfapromise rejections are unhandled. Wrap intry/catchor 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".
| 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') | ||
| }) |
There was a problem hiding this comment.
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,USERSare only added tonavItemswhen$credentialis truthy. The test never seeds the store, so onlyHOMErenders and the assertion on line 12 fails (matches the CI error).logout-buttonis rendered only inside{#if$credential != null}; in the unauthenticated state aLoginlink 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>
🤖 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.
There was a problem hiding this comment.
Actionable comments posted: 7
🧹 Nitpick comments (5)
.github/workflows/continuous_integration.yml (1)
70-71: Consider sourcingPUBLIC_AUTH_URLfrom a GitHub Actions variable instead of hardcoding.Hardcoding
http://authentication.auth-plus.appin the workflow couples CI to a single environment value and makes it easy to drift from the actual deployment configuration. Sincebuild:checkonly needs the variable to be defined for$env/static/publicresolution, 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 ishttp://(nothttps://); 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 lackrequiredandautocompleteattributes.Because the inputs have no
required(norminlength/pattern), submitting an empty form will POST blankname/passwordto the API and rely on the server to reject. Adding native validation gives immediate feedback and prevents needless requests. Also considerautocomplete="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.
**/loginmatches both the SvelteKit page route/loginand the auth API endpoint. It's harmless here only becausepage.route(...)is registered afterpage.goto('/login')inbeforeEach. 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}/loginor matching onrequest.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. Addingdata-testidattributes to the headings/labels indefault.svelteand 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
logoutrejects (e.g., network error on revocation), the rejection is unhandled and onlyconsole.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
⛔ Files ignored due to path filters (1)
package-lock.jsonis excluded by!**/package-lock.json
📒 Files selected for processing (15)
.github/workflows/continuous_integration.ymle2e/Header.test.tse2e/Login.test.tssrc/routes/+layout.sveltesrc/routes/Menu.sveltesrc/routes/login/+page.sveltesrc/routes/login/choose.sveltesrc/routes/login/code.sveltesrc/routes/login/default.sveltesrc/routes/mfa/modal.sveltesrc/routes/users/create.sveltesrc/routes/users/users.tstailwind.config.tstest/Header.svelte.test.tstest/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
| // Verify redirection and session storage | ||
| const token = await page.evaluate(() => sessionStorage.getItem('token')) | ||
| await expect(page).toHaveURL('/') | ||
| expect(token).toBe('mock-session-token') | ||
| }) |
There was a problem hiding this comment.
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.
| // 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.
| 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') | ||
| }) |
There was a problem hiding this comment.
🧩 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:
-
Strategyis 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. -
expect(page.locator('form')).not.toContainText('Welcome Back')is a weak negative assertion. Instead, assert the presence of text or elements unique tochoose.svelte, such as the "Security Verification" heading or the strategy selector with idlogin-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.
| <style> | ||
| :global(body) { | ||
| margin: 0; | ||
| /* Updated global font to match the Gallery aesthetic */ | ||
| font-family: 'Playfair Display', serif; | ||
| background-color: #f5f5f4; /* stone-100 */ | ||
| } | ||
| </style> |
There was a problem hiding this comment.
🧩 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.
| if (!$credential) { | ||
| throw new Error('credential should be setted') | ||
| } |
There was a problem hiding this comment.
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.
| 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.
| async function createNew(event: SubmitEvent) { | ||
| event.preventDefault() | ||
| if (!$credential) { | ||
| throw new Error('credential should be setted') | ||
| } | ||
| await createNewUser(name, email, password, $credential.token) | ||
| toogleModal() | ||
| } |
There was a problem hiding this comment.
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.
| 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).
| <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"> |
There was a problem hiding this comment.
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.
| <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.
Summary by CodeRabbit
New Features
UI Improvements
Bug Fixes
Tests