Skip to content

feat: customizable tax/VAT label text + a lot of other improvements and bug fixes#163

Merged
VladSez merged 54 commits intomainfrom
vldszn/2025-11-18_00-15-l16gh
Jan 11, 2026
Merged

feat: customizable tax/VAT label text + a lot of other improvements and bug fixes#163
VladSez merged 54 commits intomainfrom
vldszn/2025-11-18_00-15-l16gh

Conversation

@VladSez
Copy link
Copy Markdown
Owner

@VladSez VladSez commented Jan 2, 2026

Summary by CodeRabbit

  • New Features

    • Custom tax label and structured Tax Number fields, invoice "last updated" metadata, persistent mobile tab, Start Invoicing CTA, Stripe logo upload & payment URL, new header UI, SEO JSON‑LD.
  • Bug Fixes & Improvements

    • More resilient localStorage & URL-sharing hints, VAT→tax wording and translation updates, clearer error UI with bug‑report link, shorter CTA toast, metadata/localization refinements.
  • Removed

    • Newsletter subscribe flow, confirm-subscription pages, dev local‑storage debug UI and helpers.
  • Tests

    • Expanded E2E: canvas PDF rendering, per-test fixtures/download dirs, static-asset checks, broad multi-template/multi-language coverage.

✏️ Tip: You can customize this high-level summary in your review settings.

* Introduced a new feature allowing users to customize the VAT label text in the invoice form.
* Updated the invoice form, PDF templates, and related components to utilize the customizable VAT label.
* Enhanced validation for VAT input to accept both numeric values and specific strings.
* Improved user interface messages for clarity regarding VAT input requirements.
* Revised README.md to enhance project description, add links, and improve feature presentation.
* Updated .env.example comments for better understanding of invoice generation functionality.
* Added new screenshots for default and Stripe templates, while removing outdated images.
* Enhanced localization files with HTML span tags for better styling in descriptions across multiple languages.
* Changed all instances of VAT to Tax in the invoice form, PDF templates, and related components for consistency.
* Updated validation and schema to reflect the new tax label terminology.
* Removed the dynamic local storage view component as it was no longer needed.
* Cleaned up unused imports and components to streamline the codebase.
* Removed the local storage debug variable from .env.example for clarity.
* Added dotenv package to package.json to manage environment variables more effectively.
* Updated Playwright configuration to load environment variables from .env.local for improved testing setup.
* Enhanced VSCode settings for better TypeScript development experience.
* Cleaned up and organized various components and localization files for consistency.
* Added support for a customizable Tax Number label in the buyer and seller information sections.
* Updated related components, forms, and validation schemas to accommodate the new label functionality.
* Refactored existing tests to ensure proper handling of the Tax Number label in various scenarios.
* Enhanced user interface to allow users to set and display custom labels for Tax Number in the invoice PDF templates.
* Added a new e2e test for the About page to ensure content visibility and proper screenshot generation.
* Introduced a new command in package.json for non-flaky end-to-end tests.
* Updated Playwright configuration to improve timeout settings and worker management.
* Revised README to clarify key features and project details.
* Added a TODO file for future enhancements and refinements.
* Updated various localization files and metadata for consistency across languages.
* Refactored components for better organization and improved user experience.
@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented Jan 2, 2026

⚠️ No Changeset found

Latest commit: acf2ed5

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@vercel
Copy link
Copy Markdown

vercel Bot commented Jan 2, 2026

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

Project Deployment Review Updated (UTC)
pdf-invoice-generator Ready Ready Preview, Comment Jan 11, 2026 5:36pm

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Jan 2, 2026

📝 Walkthrough

Walkthrough

Removes newsletter subscription/backend and related UI; introduces configurable tax labels (taxLabelText, vatNoLabelText) across forms and PDFs; adds app metadata APIs persisted in localStorage; refactors PDF i18n to function-based translations; switches E2E PDF verification to canvas rendering with per-test download fixtures; assorted i18n, SEO, config, and UI adjustments.

Changes

Cohort / File(s) Summary
Subscription / Newsletter removal
src/actions/subscribe-action.ts, src/components/subscribe-input.tsx, src/app/confirm-subscription/page.tsx, src/utils/subscription-token.ts, src/app/[locale]/about/components/landing-cta-toast.tsx
Deleted subscription backend, token utils, confirm page and newsletter UI/landing CTA. Audit for any remaining references/imports.
Tax label & data model
src/app/schema/index.ts, src/app/constants/index.ts, src/components/buyer-dialog.tsx, src/components/seller-dialog.tsx, src/utils/__tests__/data.ts, src/components/buyer-management.tsx, src/components/seller-management.tsx
Added taxLabelText and vatNoLabelText to schema/constants/fixtures; seller/buyer dialogs refactored to fieldsets with label/value and visibility switches; adjust defaults and validation.
Invoice form & metadata
src/app/(app)/components/invoice-form/index.tsx, src/app/(app)/components/index.tsx, src/app/(app)/components/invoice-form/sections/*, src/app/(app)/utils/get-app-metadata.tsx, src/app/(app)/utils/invoice-number-breaking-change.ts
Wire taxLabelText through form and items; hardened VAT parsing; add getAppMetadata/updateAppMetadata and persist invoiceLastUpdatedAt; several form sections now accept explicit props. Check prop/type changes (InvoiceItems, Buyer/Seller sections).
PDF templates & PDF i18n
src/app/(app)/components/invoice-templates/**, src/app/api/generate-invoice/render-pdf-on-server.tsx, src/app/(app)/pdf-i18n-translations/pdf-translations.ts
Swap PDF translation source to INVOICE_PDF_TRANSLATIONS (now function-based supporting customTaxLabel); propagate tax label usage in PDF components; update font asset references. Validate PDF translation functions and font paths.
E2E tests & Playwright fixtures
e2e/utils/extended-playwright-test.ts, e2e/utils/render-pdf-on-canvas.ts, e2e/**/*.test.ts, e2e/default-invoice-template/*.test.ts, e2e/stripe-invoice-template/*.test.ts
Add per-test downloadDir fixture; implement renderPdfOnCanvas; migrate PDF assertions from text parsing to canvas rendering + screenshot comparisons; standardize test data and timing, mock system time. Review new test helpers and fixtures.
I18n / messages
messages/*.json, src/app/schema/i18n-schema.ts
Remove hero.noSignup, rename vatSupporttaxSupport, add buttons.startInvoicing, update hero descriptions to include markup across locales. Update i18n schema to match keys.
Config, SEO & build
src/config.ts, package.json, playwright.config.ts, tsconfig.json, .github/workflows/e2e.yml, next.config.mjs, src/app/constants/seo.ts, src/app/layout.tsx
Add STATIC_ASSETS_URL, video/fallback assets, MARKETING_FEATURES_CARDS, INVOICE_PDF_FONTS; add dotenv and bump pnpm; tweak Playwright timeouts/devices; disable TS plugin; CI runner/artifact tweaks; add SOFTWARE_APPLICATION JSON‑LD and inject into layout; update PROD/GITHUB/TWITTER/BUG URLs.
Dev utilities removed
src/app/(app)/components/dev/dev-local-storage-view.tsx, src/lib/localStorage-debug-listener.ts
Removed localStorage debug UI and monkey-patch debug listener. Search for leftover usage.
UI / small UX tweaks
src/components/footer.tsx, src/components/etc/project-logo.tsx, src/components/ui/button-helper.tsx, src/components/ui/input-helper-message.tsx, src/app/(app)/components/cta-toasts/*, src/app/(app)/components/invoice-pdf-download-link.tsx, src/app/(app)/components/invoice-pdf-preview/*, src/app/error.tsx, src/app/(app)/loading.tsx, src/components/video.tsx
Removed newsletter footer block; made ButtonHelper explicit type="button"; CTA toast timeout changed and hook renamed; added bug-report links; increased PDF preview height; inlined download tracking; accessibility/copy tweaks.
Docs & About page
README.md, TODO.md, src/app/[locale]/about/page.tsx, src/app/changelog/layout.tsx
README reworked with hero/demo assets; added TODO.md; About page redesigned with video cards and updated copy; footer copy changed from "Created by" → "Made by".

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant InvoiceForm
    participant AppMetadata
    participant LocalStorage
    participant PDFGenerator

    User->>InvoiceForm: edit or submit invoice
    InvoiceForm->>AppMetadata: updateAppMetadata(invoiceLastUpdatedAt)
    AppMetadata->>LocalStorage: write metadata JSON
    LocalStorage-->>AppMetadata: ack
    InvoiceForm->>PDFGenerator: request PDF (uses taxLabelText/vatNoLabelText)
    PDFGenerator-->>InvoiceForm: returns PDF bytes
    InvoiceForm->>LocalStorage: save invoice data
    LocalStorage-->>InvoiceForm: ack
    InvoiceForm-->>User: show "Invoice last updated" indicator
Loading
sequenceDiagram
    participant TestRunner
    participant Playwright
    participant Page
    participant TempDir as TempDownloadDir
    participant Renderer as renderPdfOnCanvas
    participant Comparator as ScreenshotComparator

    TestRunner->>Playwright: start test with extended fixture
    Playwright->>TempDir: create per-test downloadDir
    TestRunner->>Page: trigger PDF download
    Page-->>TempDir: save PDF file
    TestRunner->>Renderer: renderPdfOnCanvas(page, pdfBytes)
    Renderer->>Renderer: load pdf.js and render page to canvas
    Renderer-->>TestRunner: canvas ready
    TestRunner->>Comparator: capture & compare screenshot
    Comparator-->>TestRunner: diff result
    Playwright->>TempDir: cleanup downloadDir
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Poem

🐰 A rabbit's ode to refactors:
Subscriptions hopped away at dawn,
Labels sprout where VAT once shone,
PDFs painted on a canvas bright,
Tests guard pixels through the night,
Metadata hums — invoices wake at dawn.

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 23.91% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately reflects the main feature addition of customizable tax/VAT label text and encompasses multiple improvements and fixes throughout the codebase.

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

✨ Finishing touches
  • 📝 Generate docstrings

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

❤️ Share

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

@VladSez VladSez changed the title Vldszn/2025 11 18 00 15 l16gh feat: customizable tax/VAT label text + a lot of other improvements and bug fixes Jan 2, 2026
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 9

Caution

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

⚠️ Outside diff range comments (2)
package.json (1)

93-122: Update README to reflect removed devDependency.

The typescript-plugin-directives package was removed from devDependencies, but README.md (line 67) still references it in the "Other Tools & Services" section. Please update the documentation to maintain consistency.

src/app/(app)/components/invoice-pdf-preview/mobile-pdf-viewer.tsx (1)

108-115: Inconsistent bug report URL usage.

Line 57 uses the imported BUG_REPORT_URL constant, but line 109 hardcodes a URL with query parameters. This creates maintenance overhead if the base URL changes.

🔎 Proposed fix
                     <a
-                      href="https://pdfinvoicegenerator.userjot.com/board/bugs?cursor=1&order=top&limit=10"
+                      href={BUG_REPORT_URL}
                       target="_blank"
                       rel="noopener noreferrer"
                       className="text-blue-600 underline hover:text-blue-800"
                     >
🧹 Nitpick comments (22)
src/components/ui/button-helper.tsx (1)

13-13: Explicit type="button" is redundant but can still be overridden.

The explicit type="button" declaration is redundant since the underlying Button component already defaults to type="button" (as shown in the relevant code snippets). Additionally, because {...props} is spread after the explicit type prop, any type passed to ButtonHelper will override it.

For a helper/utility button component, it's reasonable to always enforce type="button" to prevent accidental form submissions. If that's the intent, consider either:

  1. Omitting type from the accepted props type, or
  2. Setting type="button" after the spread operator

If flexibility to override the type is intentional, the current implementation works but the explicit declaration is unnecessary.

🔎 Optional refactor to enforce type="button"

If ButtonHelper should always be type="button", exclude the type prop from the component's accepted props:

+type ButtonHelperProps = {
+  children: React.ReactNode;
+  className?: string;
+} & Omit<React.ComponentProps<typeof Button>, "type">;
+
 export const ButtonHelper = ({
   children,
   className,
   ...props
-}: { children: React.ReactNode; className?: string } & React.ComponentProps<
-  typeof Button
->) => {
+}: ButtonHelperProps) => {
   return (
     <Button
       type="button"
       variant="link"
       size="sm"
       className={cn(
         "h-5 max-w-full whitespace-normal text-pretty p-0 text-left underline",
         className,
       )}
       {...props}
     >
       {children}
     </Button>
   );
 };

Also applies to: 20-20

TODO.md (1)

3-4: Minor grammar and maintenance notes.

  1. "Double check" should be hyphenated as "Double-check" per standard grammar.
  2. Line number references (e.g., "line: 235") can drift as code evolves—consider using a searchable code comment like // TODO: verify localStorage instead.
🔎 Suggested fix
-- [ ] Double check if we need to save to local storage in the page.client.tsx (line: 235)
+- [ ] Double-check if we need to save to local storage in the page.client.tsx (search for localStorage save logic)
.prettierignore (1)

2-4: Minor: Trailing whitespace on line 2.

There's a trailing space after messages/en.d.json.ts. While it won't break anything, it's generally cleaner to remove trailing whitespace.

🔎 Suggested fix
 # i18n
-messages/en.d.json.ts 
+messages/en.d.json.ts

 pnpm-lock.yaml
src/app/(app)/hooks/use-show-random-cta-toast.tsx (1)

60-60: Address the TODO for interaction count.

The TODO correctly identifies that the current implementation triggers on a single interaction instead of requiring 2-3 meaningful interactions. This could lead to premature CTA toast displays.

Do you want me to generate an implementation that tracks multiple interactions before enabling the idle trigger?

e2e/utils/extended-playwright-test.ts (1)

27-28: Remove misleading eslint-disable comment.

The use parameter is a Playwright fixture callback, not a React hook. The react-hooks/rules-of-hooks disable comment is unnecessary and misleading.

🔎 Proposed fix
-      // eslint-disable-next-line react-hooks/rules-of-hooks
       await use(downloadDir);
src/app/(app)/components/invoice-pdf-template/invoice-vat-summary-table.tsx (1)

64-70: Consider a consistent internationalization approach.

The VAT-related headers now use the dynamic taxLabelText (lines 64, 70), while other headers like "Net" and "Pre-tax" still use translations (t.vatSummaryTable.net, t.vatSummaryTable.preTax). This creates a mixed approach.

If taxLabelText is meant to be user-customizable (not just language-specific), this is appropriate. Otherwise, consider whether all labels should follow the same pattern.

src/app/(app)/components/invoice-pdf-download-link.tsx (1)

110-113: Remove or track commented code.

There's a commented-out code block that appears to be experimental logic for skipping CTA toasts when a toast was already shown in the session. If this feature is deferred, consider creating a TODO comment or issue to track it.

Options for handling this code

Option 1: Remove if no longer needed

-        // if (isToastShownInSession) {
-        //   umamiTrackEvent("cta_toast_skipped_downloaded_invoice");
-        //   return;
-        // }
-

Option 2: Add a TODO comment if planned

-        // if (isToastShownInSession) {
-        //   umamiTrackEvent("cta_toast_skipped_downloaded_invoice");
-        //   return;
-        // }
+        // TODO: Implement session-based CTA toast deduplication
+        // Track with issue #XXX
src/app/(app)/components/invoice-pdf-preview/mobile-pdf-viewer.tsx (1)

96-101: Consider limiting re-render retries.

The onLoadError handler increments the key to force a re-render on error. Without a limit, this could cause infinite re-renders if the error persists.

🔎 Proposed fix with retry limit
+const MAX_RETRY_COUNT = 3;

// In component:
+const [retryCount, setRetryCount] = useState(0);

 onLoadError={(error) => {
   console.error(error);

-  // Force a re-render of the PDF viewer to try to recover from error
-  setKey((prev) => prev + 1);
+  // Force a re-render with retry limit to prevent infinite loops
+  if (retryCount < MAX_RETRY_COUNT) {
+    setRetryCount((prev) => prev + 1);
+    setKey((prev) => prev + 1);
+  }
 }}
src/app/[locale]/about/page.tsx (3)

40-48: Remove unused newsletter translation variables.

These variables are defined but never used since SubscribeSection is commented out on line 65. Either remove the newsletter functionality entirely or keep it for future use, but unused variables should be cleaned up.

🔎 Proposed fix
  const t = useTranslations("About");
-  const tNewsletter = useTranslations("About.newsletter");
-
-  const newsletterTitle = tNewsletter("title");
-  const newsletterDescription = tNewsletter("description");
-  const newsletterSubscribe = tNewsletter("subscribe");
-  const newsletterPlaceholder = tNewsletter("placeholder");
-  const newsletterSuccessMessage = tNewsletter("success");
-  const newsletterErrorMessage = tNewsletter("error");
-  const newsletterEmailLanguageInfo = tNewsletter("emailLanguageInfo");

Also update the Footer translations prop to remove the unused newsletter keys, or keep them if the Footer component still expects them.


516-534: Remove commented-out code.

This large block of commented-out code should be removed. Version control preserves the history if you need to reference the old implementation later.

🔎 Proposed fix
 function Logo() {
-  // const t = useTranslations("About");
-
   return (
     <div>
-      {/* <div className="flex items-center gap-1">
-        <ProjectLogo className="size-7 flex-shrink-0 sm:size-8" />
-        <div className="flex flex-col">
-          <h1 className="text-balance text-xl font-bold text-slate-800 lg:text-2xl">
-            <a
-              href="https://dub.sh/easy-invoice?ref=about-page"
-              target="_blank"
-              rel="noopener noreferrer"
-            >
-              EasyInvoicePDF
-            </a>
-          </h1>
-        </div> */}
-      {/* <ProjectLogoDescription>{t("tagline")}</ProjectLogoDescription> */}
-      {/* </div> */}
       <div className="flex items-center">

65-65: Consider removing the commented-out SubscribeSection.

If the newsletter subscription feature has been intentionally removed, delete this commented line rather than leaving it in the codebase.

src/app/(app)/utils/get-app-metadata.tsx (2)

13-18: DEFAULT_METADATA timestamps are captured at module load time.

dayjs().toISOString() is evaluated when the module is first imported, not when DEFAULT_METADATA is actually used. This means all consumers will get the same timestamp from when the module was loaded, which may be stale. Consider making this a function that generates fresh timestamps.

🔎 Proposed fix
-export const DEFAULT_METADATA = {
+export const getDefaultMetadata = (): Metadata => ({
   appVersion: APP_VERSION,
   schemaVersion: SCHEMA_VERSION,
   invoiceCreatedAt: dayjs().toISOString(),
   invoiceLastUpdatedAt: dayjs().toISOString(),
-} as const satisfies Metadata;
+});
+
+// For type-checking purposes or places needing a static reference
+export const DEFAULT_METADATA = getDefaultMetadata();

Then update updateAppMetadata to use getDefaultMetadata() instead of DEFAULT_METADATA for the reset case.


50-50: Redundant optional chaining.

After checking validatedMetadata.success, the data property is guaranteed to exist. The optional chaining is unnecessary.

🔎 Proposed fix
-    return validatedMetadata?.data;
+    return validatedMetadata.data;
src/components/seller-dialog.tsx (1)

340-350: Simplify the conditional FormMessage rendering.

The FormMessage component from react-hook-form already handles conditional rendering internally—it only displays when there's an error. The explicit conditional check is redundant.

🔎 Suggested simplification
-                        {form.formState.errors.vatNoLabelText && (
-                          <FormMessage>
-                            {form.formState.errors.vatNoLabelText.message}
-                          </FormMessage>
-                        )}
+                        <FormMessage />
e2e/default-invoice-template/pdf.test.ts (1)

58-60: Consider more deterministic waiting instead of timeout.

While the comment explains the blank screen issue, the 1-second timeout is non-deterministic. Consider waiting for a specific element or state if possible.

The current approach is pragmatic given PDF viewer rendering, but if flakiness occurs, consider:

  • Using page.waitForLoadState('networkidle') before the timeout
  • Reducing the timeout after confirming minimum stable wait time
src/app/(app)/components/invoice-form/sections/invoice-items.tsx (1)

165-166: Consider removing or uncommenting aria-label comments.

There are several commented-out aria-label attributes (lines 165, 224, 283). Either restore these for accessibility or remove the commented code to reduce noise.

Also applies to: 224-224, 283-283

src/app/(app)/components/invoice-form/index.tsx (2)

198-198: Consider using the taxLabelText default from schema instead of hardcoding.

The fallback "VAT" is hardcoded here, but the schema already defines a default. To maintain consistency, consider importing and using the schema's default value.


165-168: Consider consolidating duplicate localStorage saves.

The codebase has two separate localStorage writes for PDF_DATA_LOCAL_STORAGE_KEY:

  1. Debounced save in the child component (invoice-form/index.tsx, line 174) after form validation
  2. Save in the parent component's useEffect (page.client.tsx, line 250) when invoice data state changes

Both save the same validated data, creating unnecessary writes. While the parent's useEffect also handles URL updates and notifications, the localStorage save logic could be centralized to eliminate duplication and reduce unnecessary writes.

e2e/stripe-invoice-template/template.test.ts (2)

808-811: Consider using a more reliable wait mechanism than fixed timeout.

The waitForTimeout(1000) calls are used to wait for PDF rendering, but fixed timeouts can be flaky. Consider using waitForLoadState or polling for a specific element/condition.

🔎 Suggested approach
-    // sometimes there's a blank screen without this
-    // eslint-disable-next-line playwright/no-wait-for-timeout
-    await page.waitForTimeout(1000);
+    // Wait for PDF content to be rendered
+    await page.waitForLoadState('networkidle');

Also applies to: 937-940


613-625: Address or remove the commented-out assertions.

These TODO comments indicate that field switch visibility assertions for the Stripe template are incomplete. The commented code should either be fixed and enabled, or removed with a tracking issue created.

Would you like me to open an issue to track fixing these assertions?

src/app/(app)/components/invoice-form/sections/general-information.tsx (1)

728-729: Consider the TODO for renaming "Invoice Type" field.

The TODO suggests renaming to "Invoice Notes" which may be more intuitive. If this is a planned change, consider creating a tracking issue.

src/app/(app)/page.client.tsx (1)

248-249: Address the duplicate localStorage save TODO.

This TODO echoes the concern in invoice-form/index.tsx. Consider consolidating these saves to avoid redundant writes and potential race conditions.

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ccac646 and d2a4a9b.

⛔ Files ignored due to path filters (43)
  • .github/screenshots/default-template.png is excluded by !**/*.png
  • .github/screenshots/default.png is excluded by !**/*.png
  • .github/screenshots/easy-invoice-logo.svg is excluded by !**/*.svg
  • .github/screenshots/stripe-template.png is excluded by !**/*.png
  • .github/screenshots/stripe-with-logo.png is excluded by !**/*.png
  • .github/screenshots/stripe.png is excluded by !**/*.png
  • e2e/about-snapshot.test.ts-snapshots/about-page-english-screenshot-about-page-english-screenshot-Mobile-Chrome-darwin.png is excluded by !**/*.png
  • e2e/about-snapshot.test.ts-snapshots/about-page-english-screenshot-about-page-english-screenshot-Mobile-Safari-darwin.png is excluded by !**/*.png
  • e2e/about-snapshot.test.ts-snapshots/about-page-english-screenshot-about-page-english-screenshot-chromium-darwin.png is excluded by !**/*.png
  • e2e/default-invoice-template/pdf.test.ts-snapshots/completes-full-invoice-flow-on-mobile-pdf-playwright-screenshot-invoice-FR-2-05-2024-pdf-Mobile-Chrome-darwin.png is excluded by !**/*.png
  • e2e/default-invoice-template/pdf.test.ts-snapshots/completes-full-invoice-flow-on-mobile-pdf-playwright-screenshot-invoice-FR-2-05-2024-pdf-Mobile-Safari-darwin.png is excluded by !**/*.png
  • e2e/default-invoice-template/pdf.test.ts-snapshots/completes-full-invoice-flow-on-mobile-pdf-playwright-screenshot-invoice-FR-2-05-2024-pdf-chromium-darwin.png is excluded by !**/*.png
  • e2e/default-invoice-template/pdf.test.ts-snapshots/completes-full-invoice-flow-on-mobile-stripe-template-pdf-playwright-screenshot-stripe-invoice-FR-2-05-2024-pdf-Mobile-Chrome-darwin.png is excluded by !**/*.png
  • e2e/default-invoice-template/pdf.test.ts-snapshots/completes-full-invoice-flow-on-mobile-stripe-template-pdf-playwright-screenshot-stripe-invoice-FR-2-05-2024-pdf-Mobile-Safari-darwin.png is excluded by !**/*.png
  • e2e/default-invoice-template/pdf.test.ts-snapshots/completes-full-invoice-flow-on-mobile-stripe-template-pdf-playwright-screenshot-stripe-invoice-FR-2-05-2024-pdf-chromium-darwin.png is excluded by !**/*.png
  • e2e/default-invoice-template/pdf.test.ts-snapshots/downloads-PDF-in-English-pdf-playwright-screenshot-invoice-EN-1-12-2025-pdf-Mobile-Chrome-darwin.png is excluded by !**/*.png
  • e2e/default-invoice-template/pdf.test.ts-snapshots/downloads-PDF-in-English-pdf-playwright-screenshot-invoice-EN-1-12-2025-pdf-Mobile-Safari-darwin.png is excluded by !**/*.png
  • e2e/default-invoice-template/pdf.test.ts-snapshots/downloads-PDF-in-English-pdf-playwright-screenshot-invoice-EN-1-12-2025-pdf-chromium-darwin.png is excluded by !**/*.png
  • e2e/default-invoice-template/pdf.test.ts-snapshots/downloads-PDF-in-Polish-pdf-playwright-screenshot-invoice-PL-1-12-2025-pdf-Mobile-Chrome-darwin.png is excluded by !**/*.png
  • e2e/default-invoice-template/pdf.test.ts-snapshots/downloads-PDF-in-Polish-pdf-playwright-screenshot-invoice-PL-1-12-2025-pdf-Mobile-Safari-darwin.png is excluded by !**/*.png
  • e2e/default-invoice-template/pdf.test.ts-snapshots/downloads-PDF-in-Polish-pdf-playwright-screenshot-invoice-PL-1-12-2025-pdf-chromium-darwin.png is excluded by !**/*.png
  • e2e/default-invoice-template/pdf.test.ts-snapshots/downloads-PDF-in-Polish-stripe-template-pdf-playwright-screenshot-stripe-invoice-PL-1-12-2025-pdf-Mobile-Chrome-darwin.png is excluded by !**/*.png
  • e2e/default-invoice-template/pdf.test.ts-snapshots/downloads-PDF-in-Polish-stripe-template-pdf-playwright-screenshot-stripe-invoice-PL-1-12-2025-pdf-Mobile-Safari-darwin.png is excluded by !**/*.png
  • e2e/default-invoice-template/pdf.test.ts-snapshots/downloads-PDF-in-Polish-stripe-template-pdf-playwright-screenshot-stripe-invoice-PL-1-12-2025-pdf-chromium-darwin.png is excluded by !**/*.png
  • e2e/default-invoice-template/pdf.test.ts-snapshots/should-display-and-persist-invoice-number-in-different-languages-pdf-playwright-screenshot-invoice-PT-1-12-2025-pdf-Mobile-Chrome-darwin.png is excluded by !**/*.png
  • e2e/default-invoice-template/pdf.test.ts-snapshots/should-display-and-persist-invoice-number-in-different-languages-pdf-playwright-screenshot-invoice-PT-1-12-2025-pdf-Mobile-Safari-darwin.png is excluded by !**/*.png
  • e2e/default-invoice-template/pdf.test.ts-snapshots/should-display-and-persist-invoice-number-in-different-languages-pdf-playwright-screenshot-invoice-PT-1-12-2025-pdf-chromium-darwin.png is excluded by !**/*.png
  • e2e/default-invoice-template/pdf.test.ts-snapshots/should-display-and-persist-invoice-number-in-different-languages-stripe-template-pdf-playwright-screenshot-stripe-invoice-PT-1-12-2025-pdf-Mobile-Chrome-darwin.png is excluded by !**/*.png
  • e2e/default-invoice-template/pdf.test.ts-snapshots/should-display-and-persist-invoice-number-in-different-languages-stripe-template-pdf-playwright-screenshot-stripe-invoice-PT-1-12-2025-pdf-Mobile-Safari-darwin.png is excluded by !**/*.png
  • e2e/default-invoice-template/pdf.test.ts-snapshots/should-display-and-persist-invoice-number-in-different-languages-stripe-template-pdf-playwright-screenshot-stripe-invoice-PT-1-12-2025-pdf-chromium-darwin.png is excluded by !**/*.png
  • e2e/default-invoice-template/pdf.test.ts-snapshots/update-pdf-when-invoice-data-changes-pdf-playwright-screenshot-invoice-EN-1-12-2025-pdf-Mobile-Chrome-darwin.png is excluded by !**/*.png
  • e2e/default-invoice-template/pdf.test.ts-snapshots/update-pdf-when-invoice-data-changes-pdf-playwright-screenshot-invoice-EN-1-12-2025-pdf-Mobile-Safari-darwin.png is excluded by !**/*.png
  • e2e/default-invoice-template/pdf.test.ts-snapshots/update-pdf-when-invoice-data-changes-pdf-playwright-screenshot-invoice-EN-1-12-2025-pdf-chromium-darwin.png is excluded by !**/*.png
  • e2e/default-invoice-template/pdf.test.ts-snapshots/update-pdf-when-invoice-data-changes-stripe-template-pdf-playwright-screenshot-stripe-invoice-EN-1-12-2025-pdf-Mobile-Chrome-darwin.png is excluded by !**/*.png
  • e2e/default-invoice-template/pdf.test.ts-snapshots/update-pdf-when-invoice-data-changes-stripe-template-pdf-playwright-screenshot-stripe-invoice-EN-1-12-2025-pdf-Mobile-Safari-darwin.png is excluded by !**/*.png
  • e2e/default-invoice-template/pdf.test.ts-snapshots/update-pdf-when-invoice-data-changes-stripe-template-pdf-playwright-screenshot-stripe-invoice-EN-1-12-2025-pdf-chromium-darwin.png is excluded by !**/*.png
  • e2e/stripe-invoice-template/template.test.ts-snapshots/automatically-enables-VAT-field-visibility-and-sets-date-format-when-switching-to-Stripe-template-pdf-playwright-screenshot-invoice-EN-1-12-2025-pdf-Mobile-Chrome-darwin.png is excluded by !**/*.png
  • e2e/stripe-invoice-template/template.test.ts-snapshots/automatically-enables-VAT-field-visibility-and-sets-date-format-when-switching-to-Stripe-template-pdf-playwright-screenshot-invoice-EN-1-12-2025-pdf-Mobile-Safari-darwin.png is excluded by !**/*.png
  • e2e/stripe-invoice-template/template.test.ts-snapshots/automatically-enables-VAT-field-visibility-and-sets-date-format-when-switching-to-Stripe-template-pdf-playwright-screenshot-invoice-EN-1-12-2025-pdf-chromium-darwin.png is excluded by !**/*.png
  • e2e/stripe-invoice-template/template.test.ts-snapshots/pdf-with-logo-and-payment-url-when-using-stripe-template-pdf-playwright-screenshot-invoice-EN-1-12-2025-pdf-Mobile-Chrome-darwin.png is excluded by !**/*.png
  • e2e/stripe-invoice-template/template.test.ts-snapshots/pdf-with-logo-and-payment-url-when-using-stripe-template-pdf-playwright-screenshot-invoice-EN-1-12-2025-pdf-Mobile-Safari-darwin.png is excluded by !**/*.png
  • e2e/stripe-invoice-template/template.test.ts-snapshots/pdf-with-logo-and-payment-url-when-using-stripe-template-pdf-playwright-screenshot-invoice-EN-1-12-2025-pdf-chromium-darwin.png is excluded by !**/*.png
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (79)
  • .env.example
  • .prettierignore
  • .vscode/extensions.json
  • .vscode/settings.json
  • README.md
  • TODO.md
  • e2e/about-snapshot.test.ts
  • e2e/about.test.ts
  • e2e/buyer.test.ts
  • e2e/default-invoice-template/pdf.test.ts
  • e2e/generate-invoice-link.test.ts
  • e2e/invoice-form.test.ts
  • e2e/seller.test.ts
  • e2e/stripe-invoice-template/template.test.ts
  • e2e/utils/extended-playwright-test.ts
  • messages/de.json
  • messages/en.json
  • messages/es.json
  • messages/fr.json
  • messages/it.json
  • messages/nl.json
  • messages/pl.json
  • messages/pt.json
  • messages/ru.json
  • messages/uk.json
  • package.json
  • playwright.config.ts
  • src/actions/subscribe-action.ts
  • src/app/(app)/components/cta-toasts/index.tsx
  • src/app/(app)/components/dev/dev-local-storage-view.tsx
  • src/app/(app)/components/index.tsx
  • src/app/(app)/components/invoice-form/index.tsx
  • src/app/(app)/components/invoice-form/sections/buyer-information.tsx
  • src/app/(app)/components/invoice-form/sections/general-information.tsx
  • src/app/(app)/components/invoice-form/sections/invoice-items.tsx
  • src/app/(app)/components/invoice-form/sections/seller-information.tsx
  • src/app/(app)/components/invoice-pdf-download-link.tsx
  • src/app/(app)/components/invoice-pdf-preview/desktop-pdf-viewer.tsx
  • src/app/(app)/components/invoice-pdf-preview/mobile-pdf-viewer.tsx
  • src/app/(app)/components/invoice-pdf-stripe-template/stripe-footer.tsx
  • src/app/(app)/components/invoice-pdf-stripe-template/stripe-items-table.tsx
  • src/app/(app)/components/invoice-pdf-stripe-template/stripe-seller-buyer-info.tsx
  • src/app/(app)/components/invoice-pdf-stripe-template/stripe-totals.tsx
  • src/app/(app)/components/invoice-pdf-template/invoice-footer.tsx
  • src/app/(app)/components/invoice-pdf-template/invoice-items-table.tsx
  • src/app/(app)/components/invoice-pdf-template/invoice-seller-buyer-info.tsx
  • src/app/(app)/components/invoice-pdf-template/invoice-vat-summary-table.tsx
  • src/app/(app)/error.tsx
  • src/app/(app)/hooks/use-show-random-cta-toast.tsx
  • src/app/(app)/loading.tsx
  • src/app/(app)/page.client.tsx
  • src/app/(app)/page.tsx
  • src/app/(app)/utils/get-app-metadata.tsx
  • src/app/[locale]/about/components/landing-cta-toast.tsx
  • src/app/[locale]/about/page.tsx
  • src/app/api/generate-invoice/render-pdf-on-server.tsx
  • src/app/changelog/layout.tsx
  • src/app/confirm-subscription/page.tsx
  • src/app/constants/index.ts
  • src/app/constants/seo.ts
  • src/app/layout.tsx
  • src/app/schema/i18n-schema.ts
  • src/app/schema/index.ts
  • src/components/buyer-dialog.tsx
  • src/components/buyer-management.tsx
  • src/components/etc/project-logo.tsx
  • src/components/footer.tsx
  • src/components/seller-dialog.tsx
  • src/components/seller-management.tsx
  • src/components/subscribe-input.tsx
  • src/components/ui/button-helper.tsx
  • src/components/ui/input-helper-message.tsx
  • src/components/video.tsx
  • src/config.ts
  • src/lib/localStorage-debug-listener.ts
  • src/utils/__tests__/data.ts
  • src/utils/subscription-token.ts
  • src/utils/url-compression.ts
  • tsconfig.json
💤 Files with no reviewable changes (7)
  • src/utils/subscription-token.ts
  • src/app/(app)/components/dev/dev-local-storage-view.tsx
  • src/lib/localStorage-debug-listener.ts
  • src/actions/subscribe-action.ts
  • src/components/subscribe-input.tsx
  • src/app/[locale]/about/components/landing-cta-toast.tsx
  • src/app/confirm-subscription/page.tsx
🧰 Additional context used
🧠 Learnings (30)
📚 Learning: 2025-07-20T22:00:37.696Z
Learnt from: CR
Repo: VladSez/easy-invoice-pdf PR: 0
File: .cursor/rules/testing.mdc:0-0
Timestamp: 2025-07-20T22:00:37.696Z
Learning: Applies to **/*.test.{ts,tsx} : Integration tests must cover key user flows

Applied to files:

  • e2e/about-snapshot.test.ts
  • e2e/default-invoice-template/pdf.test.ts
  • e2e/buyer.test.ts
  • e2e/stripe-invoice-template/template.test.ts
📚 Learning: 2025-07-20T22:00:37.696Z
Learnt from: CR
Repo: VladSez/easy-invoice-pdf PR: 0
File: .cursor/rules/testing.mdc:0-0
Timestamp: 2025-07-20T22:00:37.696Z
Learning: Applies to **/*.test.{ts,tsx} : Use meaningful assertions in tests

Applied to files:

  • e2e/about-snapshot.test.ts
  • e2e/default-invoice-template/pdf.test.ts
  • e2e/stripe-invoice-template/template.test.ts
  • e2e/invoice-form.test.ts
  • e2e/utils/extended-playwright-test.ts
📚 Learning: 2025-07-20T22:00:37.696Z
Learnt from: CR
Repo: VladSez/easy-invoice-pdf PR: 0
File: .cursor/rules/testing.mdc:0-0
Timestamp: 2025-07-20T22:00:37.696Z
Learning: Applies to **/*.test.{ts,tsx} : Use descriptive test names

Applied to files:

  • e2e/about-snapshot.test.ts
  • e2e/default-invoice-template/pdf.test.ts
  • e2e/stripe-invoice-template/template.test.ts
  • e2e/utils/extended-playwright-test.ts
📚 Learning: 2025-07-20T22:00:37.696Z
Learnt from: CR
Repo: VladSez/easy-invoice-pdf PR: 0
File: .cursor/rules/testing.mdc:0-0
Timestamp: 2025-07-20T22:00:37.696Z
Learning: Use Playwright for E2E testing

Applied to files:

  • e2e/about-snapshot.test.ts
  • e2e/default-invoice-template/pdf.test.ts
  • package.json
  • e2e/utils/extended-playwright-test.ts
  • playwright.config.ts
📚 Learning: 2025-07-20T21:59:36.257Z
Learnt from: CR
Repo: VladSez/easy-invoice-pdf PR: 0
File: .cursor/rules/error-handling.mdc:0-0
Timestamp: 2025-07-20T21:59:36.257Z
Learning: Applies to **/*.tsx : Show generic user-friendly fallback UI for unexpected errors

Applied to files:

  • src/app/(app)/error.tsx
  • src/app/(app)/components/invoice-pdf-preview/desktop-pdf-viewer.tsx
  • src/app/(app)/components/invoice-pdf-preview/mobile-pdf-viewer.tsx
📚 Learning: 2025-07-20T21:59:36.257Z
Learnt from: CR
Repo: VladSez/easy-invoice-pdf PR: 0
File: .cursor/rules/error-handling.mdc:0-0
Timestamp: 2025-07-20T21:59:36.257Z
Learning: Applies to **/*.tsx : Display user-friendly messages for expected errors

Applied to files:

  • src/app/(app)/error.tsx
📚 Learning: 2025-07-20T21:59:36.257Z
Learnt from: CR
Repo: VladSez/easy-invoice-pdf PR: 0
File: .cursor/rules/error-handling.mdc:0-0
Timestamp: 2025-07-20T21:59:36.257Z
Learning: Applies to **/*.tsx : Use error boundaries for unexpected errors

Applied to files:

  • src/app/(app)/error.tsx
📚 Learning: 2025-07-20T22:00:37.696Z
Learnt from: CR
Repo: VladSez/easy-invoice-pdf PR: 0
File: .cursor/rules/testing.mdc:0-0
Timestamp: 2025-07-20T22:00:37.696Z
Learning: Applies to **/*.test.{ts,tsx} : Test error cases

Applied to files:

  • src/app/(app)/error.tsx
  • e2e/default-invoice-template/pdf.test.ts
  • e2e/stripe-invoice-template/template.test.ts
  • e2e/utils/extended-playwright-test.ts
📚 Learning: 2025-07-20T21:59:36.257Z
Learnt from: CR
Repo: VladSez/easy-invoice-pdf PR: 0
File: .cursor/rules/error-handling.mdc:0-0
Timestamp: 2025-07-20T21:59:36.257Z
Learning: Applies to **/*.tsx : Wrap React components with ErrorBoundary and provide a FallbackComponent for error handling

Applied to files:

  • src/app/(app)/error.tsx
📚 Learning: 2025-07-20T21:59:36.257Z
Learnt from: CR
Repo: VladSez/easy-invoice-pdf PR: 0
File: .cursor/rules/error-handling.mdc:0-0
Timestamp: 2025-07-20T21:59:36.257Z
Learning: Applies to **/*.tsx : Catch unexpected errors (runtime errors, network issues) with error boundaries

Applied to files:

  • src/app/(app)/error.tsx
📚 Learning: 2025-07-20T22:00:19.380Z
Learnt from: CR
Repo: VladSez/easy-invoice-pdf PR: 0
File: .cursor/rules/shared.mdc:0-0
Timestamp: 2025-07-20T22:00:19.380Z
Learning: Applies to **/*.{ts,tsx} : Use TypeScript for all code; prefer interfaces over types.

Applied to files:

  • tsconfig.json
  • .vscode/settings.json
📚 Learning: 2025-07-20T22:00:19.380Z
Learnt from: CR
Repo: VladSez/easy-invoice-pdf PR: 0
File: .cursor/rules/shared.mdc:0-0
Timestamp: 2025-07-20T22:00:19.380Z
Learning: Applies to **/*.tsx : Use Shadcn UI, Radix, and Tailwind for components and styling.

Applied to files:

  • src/app/[locale]/about/page.tsx
  • .vscode/settings.json
  • src/app/(app)/components/invoice-form/sections/general-information.tsx
  • package.json
📚 Learning: 2025-07-20T22:00:19.380Z
Learnt from: CR
Repo: VladSez/easy-invoice-pdf PR: 0
File: .cursor/rules/shared.mdc:0-0
Timestamp: 2025-07-20T22:00:19.380Z
Learning: Structure files: exported component, subcomponents, helpers, static content, types.

Applied to files:

  • src/app/[locale]/about/page.tsx
📚 Learning: 2025-07-20T21:59:36.257Z
Learnt from: CR
Repo: VladSez/easy-invoice-pdf PR: 0
File: .cursor/rules/error-handling.mdc:0-0
Timestamp: 2025-07-20T21:59:36.257Z
Learning: Applies to **/*.{ts,tsx} : Use Zod schemas and z.infer for form validation and type inference in TypeScript

Applied to files:

  • .vscode/settings.json
  • src/app/schema/index.ts
📚 Learning: 2025-07-20T21:59:36.257Z
Learnt from: CR
Repo: VladSez/easy-invoice-pdf PR: 0
File: .cursor/rules/error-handling.mdc:0-0
Timestamp: 2025-07-20T21:59:36.257Z
Learning: Applies to **/*.{ts,tsx} : Use Zod for form validation and type inference

Applied to files:

  • .vscode/settings.json
  • src/components/seller-dialog.tsx
  • src/components/buyer-dialog.tsx
📚 Learning: 2025-07-20T22:00:19.380Z
Learnt from: CR
Repo: VladSez/easy-invoice-pdf PR: 0
File: .cursor/rules/shared.mdc:0-0
Timestamp: 2025-07-20T22:00:19.380Z
Learning: Applies to **/*.tsx : Favor named exports for components.

Applied to files:

  • .vscode/settings.json
  • src/app/(app)/components/invoice-form/sections/buyer-information.tsx
📚 Learning: 2025-07-20T22:00:19.380Z
Learnt from: CR
Repo: VladSez/easy-invoice-pdf PR: 0
File: .cursor/rules/shared.mdc:0-0
Timestamp: 2025-07-20T22:00:19.380Z
Learning: Applies to **/*.{ts,tsx} : Use `console.log({})` syntax for debugging.

Applied to files:

  • .vscode/settings.json
📚 Learning: 2025-07-20T22:00:37.696Z
Learnt from: CR
Repo: VladSez/easy-invoice-pdf PR: 0
File: .cursor/rules/testing.mdc:0-0
Timestamp: 2025-07-20T22:00:37.696Z
Learning: Applies to **/*.test.{ts,tsx} : Use test data factories in tests

Applied to files:

  • e2e/generate-invoice-link.test.ts
  • e2e/default-invoice-template/pdf.test.ts
  • e2e/buyer.test.ts
  • e2e/stripe-invoice-template/template.test.ts
  • src/utils/__tests__/data.ts
  • e2e/utils/extended-playwright-test.ts
📚 Learning: 2025-07-20T22:00:37.696Z
Learnt from: CR
Repo: VladSez/easy-invoice-pdf PR: 0
File: .cursor/rules/testing.mdc:0-0
Timestamp: 2025-07-20T22:00:37.696Z
Learning: Applies to **/*.test.{ts,tsx} : Follow AAA pattern (Arrange, Act, Assert) in tests

Applied to files:

  • e2e/default-invoice-template/pdf.test.ts
  • e2e/stripe-invoice-template/template.test.ts
  • e2e/utils/extended-playwright-test.ts
📚 Learning: 2025-07-20T22:00:37.696Z
Learnt from: CR
Repo: VladSez/easy-invoice-pdf PR: 0
File: .cursor/rules/testing.mdc:0-0
Timestamp: 2025-07-20T22:00:37.696Z
Learning: Applies to **/*.test.{ts,tsx} : Use MSW for API mocking in tests

Applied to files:

  • e2e/default-invoice-template/pdf.test.ts
  • e2e/stripe-invoice-template/template.test.ts
  • src/utils/__tests__/data.ts
  • e2e/utils/extended-playwright-test.ts
  • playwright.config.ts
📚 Learning: 2025-07-20T22:00:37.696Z
Learnt from: CR
Repo: VladSez/easy-invoice-pdf PR: 0
File: .cursor/rules/testing.mdc:0-0
Timestamp: 2025-07-20T22:00:37.696Z
Learning: Applies to **/*.test.{ts,tsx} : Mock external dependencies in tests

Applied to files:

  • e2e/default-invoice-template/pdf.test.ts
  • e2e/stripe-invoice-template/template.test.ts
  • e2e/utils/extended-playwright-test.ts
  • playwright.config.ts
📚 Learning: 2025-07-20T22:00:19.380Z
Learnt from: CR
Repo: VladSez/easy-invoice-pdf PR: 0
File: .cursor/rules/shared.mdc:0-0
Timestamp: 2025-07-20T22:00:19.380Z
Learning: Applies to **/*.tsx : Implement responsive design with Tailwind CSS; use a mobile-first approach.

Applied to files:

  • src/app/(app)/components/index.tsx
📚 Learning: 2025-07-20T21:59:36.257Z
Learnt from: CR
Repo: VladSez/easy-invoice-pdf PR: 0
File: .cursor/rules/error-handling.mdc:0-0
Timestamp: 2025-07-20T21:59:36.257Z
Learning: Applies to **/*.tsx : Implement client-side validation for immediate feedback

Applied to files:

  • src/app/(app)/components/invoice-form/sections/buyer-information.tsx
📚 Learning: 2025-07-20T22:00:19.380Z
Learnt from: CR
Repo: VladSez/easy-invoice-pdf PR: 0
File: .cursor/rules/shared.mdc:0-0
Timestamp: 2025-07-20T22:00:19.380Z
Learning: Applies to **/*.tsx : Use functional components with TypeScript interfaces.

Applied to files:

  • src/app/(app)/components/invoice-form/sections/buyer-information.tsx
📚 Learning: 2025-07-20T22:00:19.380Z
Learnt from: CR
Repo: VladSez/easy-invoice-pdf PR: 0
File: .cursor/rules/shared.mdc:0-0
Timestamp: 2025-07-20T22:00:19.380Z
Learning: Use pnpm.

Applied to files:

  • package.json
📚 Learning: 2025-07-20T22:00:37.696Z
Learnt from: CR
Repo: VladSez/easy-invoice-pdf PR: 0
File: .cursor/rules/testing.mdc:0-0
Timestamp: 2025-07-20T22:00:37.696Z
Learning: Applies to **/*.test.{ts,tsx} : Use Vitest for unit and integration tests

Applied to files:

  • package.json
📚 Learning: 2025-07-20T22:00:37.696Z
Learnt from: CR
Repo: VladSez/easy-invoice-pdf PR: 0
File: .cursor/rules/testing.mdc:0-0
Timestamp: 2025-07-20T22:00:37.696Z
Learning: Applies to **/*.test.{ts,tsx} : Keep tests isolated

Applied to files:

  • e2e/utils/extended-playwright-test.ts
  • playwright.config.ts
📚 Learning: 2025-07-20T22:00:37.696Z
Learnt from: CR
Repo: VladSez/easy-invoice-pdf PR: 0
File: .cursor/rules/testing.mdc:0-0
Timestamp: 2025-07-20T22:00:37.696Z
Learning: Applies to **/*.test.{ts,tsx} : Group related tests in describe blocks

Applied to files:

  • e2e/utils/extended-playwright-test.ts
📚 Learning: 2025-07-20T22:00:37.696Z
Learnt from: CR
Repo: VladSez/easy-invoice-pdf PR: 0
File: .cursor/rules/testing.mdc:0-0
Timestamp: 2025-07-20T22:00:37.696Z
Learning: Applies to **/*.test.{ts,tsx} : Test cases should use it('should do something specific', () => {})

Applied to files:

  • e2e/utils/extended-playwright-test.ts
📚 Learning: 2025-07-20T21:59:36.257Z
Learnt from: CR
Repo: VladSez/easy-invoice-pdf PR: 0
File: .cursor/rules/error-handling.mdc:0-0
Timestamp: 2025-07-20T21:59:36.257Z
Learning: Applies to **/*.{ts,tsx} : Define reusable validation schemas

Applied to files:

  • src/app/schema/index.ts
🧬 Code graph analysis (29)
e2e/about-snapshot.test.ts (1)
e2e/utils/extended-playwright-test.ts (1)
  • test (17-33)
src/app/(app)/error.tsx (1)
src/config.ts (1)
  • BUG_REPORT_URL (22-23)
src/app/(app)/components/invoice-pdf-stripe-template/stripe-footer.tsx (1)
src/config.ts (1)
  • PROD_WEBSITE_URL (16-16)
src/components/footer.tsx (1)
src/components/etc/project-logo.tsx (1)
  • ProjectLogo (3-26)
src/app/(app)/components/cta-toasts/index.tsx (2)
src/app/[locale]/about/components/landing-cta-toast.tsx (3)
  • showCTAToast (11-24)
  • showRandomCTAToast (12-16)
  • clearTimeout (21-23)
src/app/(app)/contexts/cta-toast-context.tsx (1)
  • CTAToastContextValue (5-8)
src/app/(app)/components/invoice-form/sections/invoice-items.tsx (2)
src/app/(app)/components/invoice-form/common.tsx (1)
  • ErrorMessage (3-5)
src/components/ui/input-helper-message.tsx (1)
  • InputHelperMessage (3-17)
e2e/generate-invoice-link.test.ts (1)
src/app/schema/index.ts (2)
  • SellerData (533-533)
  • BuyerData (574-574)
messages/es.json (1)
src/app/(app)/utils/invoice-number-breaking-change.ts (1)
  • handleInvoiceNumberBreakingChange (18-66)
src/app/(app)/utils/get-app-metadata.tsx (1)
src/app/schema/index.ts (5)
  • APP_VERSION (345-345)
  • SCHEMA_VERSION (352-352)
  • Metadata (803-803)
  • METADATA_LOCAL_STORAGE_KEY (805-805)
  • metadataSchema (783-801)
e2e/default-invoice-template/pdf.test.ts (2)
e2e/utils/extended-playwright-test.ts (1)
  • test (17-33)
e2e/stripe-invoice-template/share-logic.test.ts (1)
  • test (5-372)
src/app/(app)/components/invoice-pdf-preview/desktop-pdf-viewer.tsx (1)
src/config.ts (1)
  • BUG_REPORT_URL (22-23)
src/app/(app)/components/invoice-pdf-template/invoice-footer.tsx (1)
src/config.ts (1)
  • PROD_WEBSITE_URL (16-16)
src/components/seller-dialog.tsx (1)
src/components/ui/input-helper-message.tsx (1)
  • InputHelperMessage (3-17)
src/utils/url-compression.ts (1)
src/utils/__tests__/url-compression.test.ts (3)
  • it (525-628)
  • key (613-617)
  • key (557-561)
e2e/buyer.test.ts (1)
src/app/(app)/components/invoice-form/sections/buyer-information.tsx (1)
  • BUYER_TOOLTIP_CONTENT (24-25)
messages/pt.json (1)
src/app/(app)/utils/__tests__/invoice-number-breaking-change.test.ts (1)
  • it (18-109)
src/app/(app)/components/index.tsx (2)
src/app/schema/index.ts (2)
  • MOBILE_TABS_VALUES (775-775)
  • DEFAULT_MOBILE_TAB (776-776)
src/app/(app)/utils/get-app-metadata.tsx (2)
  • getAppMetadata (30-58)
  • updateAppMetadata (81-116)
src/components/etc/project-logo.tsx (1)
src/components/etc/github-logo.tsx (1)
  • GithubIcon (3-18)
src/components/buyer-dialog.tsx (5)
src/components/ui/tooltip.tsx (1)
  • CustomTooltip (94-94)
src/components/ui/label.tsx (1)
  • Label (24-24)
src/components/ui/form.tsx (4)
  • FormItem (176-176)
  • FormLabel (177-177)
  • FormControl (178-178)
  • FormMessage (180-180)
src/components/ui/input.tsx (1)
  • Input (29-29)
src/components/ui/input-helper-message.tsx (1)
  • InputHelperMessage (3-17)
src/app/(app)/components/invoice-form/sections/buyer-information.tsx (7)
src/app/(app)/components/invoice-form/common.tsx (1)
  • ErrorMessage (3-5)
src/components/ui/switch.tsx (1)
  • Switch (34-34)
src/components/ui/tooltip.tsx (1)
  • CustomTooltip (94-94)
src/components/ui/label.tsx (1)
  • Label (24-24)
src/components/label-with-edit-icon.tsx (1)
  • LabelWithEditIcon (14-56)
src/components/ui/input.tsx (1)
  • Input (29-29)
src/components/ui/input-helper-message.tsx (1)
  • InputHelperMessage (3-17)
src/app/constants/seo.ts (1)
src/config.ts (1)
  • STATIC_ASSETS_URL (8-8)
src/app/(app)/components/invoice-pdf-preview/mobile-pdf-viewer.tsx (1)
src/config.ts (1)
  • BUG_REPORT_URL (22-23)
src/components/ui/button-helper.tsx (2)
src/components/ui/button.tsx (2)
  • ButtonProps (38-42)
  • props (48-61)
src/components/subscribe-input.tsx (1)
  • SubmitButton (12-42)
e2e/seller.test.ts (2)
src/app/(app)/components/invoice-form/sections/seller-information.tsx (1)
  • SELLER_TOOLTIP_CONTENT (22-23)
src/app/constants/index.ts (1)
  • DEFAULT_SELLER_DATA (30-49)
src/app/(app)/components/invoice-pdf-download-link.tsx (3)
src/lib/umami-analytics-track-event.tsx (1)
  • umamiTrackEvent (29-62)
src/components/github-star-cta.tsx (1)
  • umamiTrackEvent (18-20)
src/app/(app)/utils/__tests__/invoice-number-breaking-change.test.ts (1)
  • umamiTrackEvent (7-9)
e2e/invoice-form.test.ts (1)
src/app/(app)/utils/__tests__/invoice-number-breaking-change.test.ts (5)
  • it (18-109)
  • it (342-371)
  • it (173-265)
  • input (268-290)
  • input (19-43)
src/app/layout.tsx (1)
src/app/constants/seo.ts (1)
  • SOFTWARE_APPLICATION_JSONLD (122-133)
src/app/(app)/components/invoice-form/sections/seller-information.tsx (7)
src/app/(app)/components/invoice-form/common.tsx (1)
  • ErrorMessage (3-5)
src/components/ui/switch.tsx (1)
  • Switch (34-34)
src/components/ui/tooltip.tsx (1)
  • CustomTooltip (94-94)
src/components/ui/label.tsx (1)
  • Label (24-24)
src/components/label-with-edit-icon.tsx (1)
  • LabelWithEditIcon (14-56)
src/components/ui/input.tsx (1)
  • Input (29-29)
src/components/ui/input-helper-message.tsx (1)
  • InputHelperMessage (3-17)
src/app/schema/index.ts (2)
src/app/(app)/utils/invoice-number-breaking-change.ts (1)
  • handleInvoiceNumberBreakingChange (18-66)
src/app/(app)/utils/__tests__/invoice-number-breaking-change.test.ts (2)
  • it (342-371)
  • input (83-108)
🪛 ast-grep (0.40.3)
src/app/layout.tsx

[warning] 138-138: Usage of dangerouslySetInnerHTML detected. This bypasses React's built-in XSS protection. Always sanitize HTML content using libraries like DOMPurify before injecting it into the DOM to prevent XSS attacks.
Context: dangerouslySetInnerHTML
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://reactjs.org/docs/dom-elements.html#dangerouslysetinnerhtml
- https://cwe.mitre.org/data/definitions/79.html

(react-unsafe-html-injection)

🪛 Biome (2.1.2)
src/app/layout.tsx

[error] 139-139: Avoid passing content using the dangerouslySetInnerHTML prop.

Setting content using code can expose users to cross-site scripting (XSS) attacks

(lint/security/noDangerouslySetInnerHtml)

e2e/utils/extended-playwright-test.ts

[error] 20-20: Unexpected empty object pattern.

(lint/correctness/noEmptyPattern)

🪛 LanguageTool
TODO.md

[grammar] ~4-~4: Use a hyphen to join words.
Context: ... invoice form (better name) - [ ] Double check if we need to save to local storag...

(QB_NEW_EN_HYPHEN)

🪛 markdownlint-cli2 (0.18.1)
README.md

47-47: Bare URL used

(MD034, no-bare-urls)

Comment thread e2e/generate-invoice-link.test.ts
Comment thread e2e/utils/extended-playwright-test.ts
Comment thread playwright.config.ts Outdated
Comment on lines +18 to +21
// @ts-expect-error - NODE_ENV is not defined in the environment variables
const isLocal = process.env.NODE_ENV === "local";

const TIMEOUT = isLocal ? 35_000 : 80_000; // 80 seconds
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Search for NODE_ENV references and "local" value
rg -n 'NODE_ENV.*local' -g '!node_modules' -g '!playwright-output'

Repository: VladSez/easy-invoice-pdf

Length of output: 141


🏁 Script executed:

#!/bin/bash
# More broadly search for NODE_ENV references
rg -n 'NODE_ENV' -g '!node_modules' -g '!playwright-output' | head -30

Repository: VladSez/easy-invoice-pdf

Length of output: 340


🏁 Script executed:

#!/bin/bash
# Check for environment configuration files
fd -e '.env*' -e '*.json' -e '*.config.*' | grep -E '(env|config)' | head -20

Repository: VladSez/easy-invoice-pdf

Length of output: 50


🏁 Script executed:

#!/bin/bash
# Read the playwright.config.ts file to understand the context
head -40 playwright.config.ts

Repository: VladSez/easy-invoice-pdf

Length of output: 1382


🏁 Script executed:

#!/bin/bash
# Check if .env.local file exists
ls -la .env.local 2>/dev/null || echo "No .env.local file found"

Repository: VladSez/easy-invoice-pdf

Length of output: 92


🏁 Script executed:

#!/bin/bash
# Search for any documentation or comments about NODE_ENV
rg -n 'NODE_ENV|environment' -g '!node_modules' -g '!dist' README.md .env* package.json 2>/dev/null | head -20

Repository: VladSez/easy-invoice-pdf

Length of output: 50


🏁 Script executed:

#!/bin/bash
# Check for TypeScript environment type declarations
find . -name "*.d.ts" -o -name "env.ts" -o -name "types.ts" | xargs grep -l "NODE_ENV" 2>/dev/null || echo "No type definitions found for NODE_ENV"

Repository: VladSez/easy-invoice-pdf

Length of output: 106


🏁 Script executed:

#!/bin/bash
# Check package.json for scripts that might set NODE_ENV
grep -A 5 '"scripts"' package.json | head -20

Repository: VladSez/easy-invoice-pdf

Length of output: 254


Remove unreachable condition or document required environment setup.

The code checks for NODE_ENV === "local" to apply a 35-second timeout for local development, but .env.local doesn't exist and NODE_ENV is never set to "local" anywhere in the project. This means isLocal always evaluates to false, making the local timeout configuration unreachable dead code.

Either:

  • Create .env.local with NODE_ENV=local and document this setup requirement for developers, or
  • Remove the unused isLocal condition and use a single timeout value

The @ts-expect-error suppression is a symptom of the deeper issue—process.env should be properly typed if custom environment variables are needed.

🤖 Prompt for AI Agents
In playwright.config.ts around lines 18 to 21, the isLocal branch (NODE_ENV ===
"local") is unreachable because .env.local and NODE_ENV=local are not provided;
either remove the conditional and use a single TIMEOUT value (keep the larger
80_000 or choose a single desired timeout) and delete the @ts-expect-error, or
add documentation and a .env.local file (and project bootstrap docs) that set
NODE_ENV=local and properly type custom env vars so process.env checks are
valid; update the file accordingly and remove the ts-expect-error once
environment typing is handled.

Comment thread src/app/(app)/components/index.tsx
Comment thread src/app/(app)/components/index.tsx
Comment on lines +186 to +187
// Always enable VAT field visibility for Stripe template (because we don't show Switches for items in Stripe template and we want to make sure the Tax column is visible in the PDF)
setValue("items.0.vatFieldIsVisible", true);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Hardcoded index items.0.vatFieldIsVisible may not cover all items.

When switching to Stripe template, only the first item's VAT visibility is set. If there are multiple items, the others won't have their visibility updated.

🔎 Consider updating all items

You may want to iterate over all items or use a different approach:

// Option: Set visibility for all items via a form-level field
// or iterate if needed
🤖 Prompt for AI Agents
In src/app/(app)/components/invoice-form/sections/general-information.tsx around
lines 186-187, the code sets VAT visibility only for items.0 which only affects
the first item; update the logic to set VAT visibility for every item instead of
a hardcoded index — e.g., read the current items array (via getValues or watch),
loop through each item index and call setValue(`items.${i}.vatFieldIsVisible`,
true) for each, or alternatively introduce a form-level flag (e.g.,
itemsVatFieldIsVisible) and set that instead and update item render logic to
respect the form-level flag so all items show VAT when switching to the Stripe
template.

Comment thread src/app/(app)/components/invoice-form/sections/invoice-items.tsx Outdated
<Text style={styles.tableCellBold}>
{t.invoiceItemsTable.vatAmount}
</Text>
<Text style={styles.tableCellBold}>{taxLabelText} Amount</Text>
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Hardcoded "Amount" may break i18n consistency.

The header uses ${taxLabelText} Amount where "Amount" is hardcoded in English. Since the invoice supports 10+ languages via TRANSLATIONS, this could result in mixed-language headers (e.g., "MwSt. Amount" for German invoices).

Consider using the existing translation system for consistency:

-              <Text style={styles.tableCellBold}>{taxLabelText} Amount</Text>
+              <Text style={styles.tableCellBold}>{taxLabelText} {t.invoiceItemsTable.amount}</Text>

Or introduce a dedicated translation key like taxAmount if the semantics differ from the existing "amount" field.

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In src/app/(app)/components/invoice-pdf-template/invoice-items-table.tsx around
line 110, the header uses a hardcoded English word "Amount" concatenated with
taxLabelText which breaks i18n; replace the literal with the translated string
from the existing translation map (e.g., use TRANSLATIONS[locale].amount) or, if
semantics differ, add a new translation key like taxAmount to the TRANSLATIONS
object and use that key so the header reads `${taxLabelText}
${translatedTaxAmount}` and remains correct for all locales.

@@ -14,6 +14,7 @@ export function InvoiceVATSummaryTable({
}) {
const language = invoiceData.language;
const t = TRANSLATIONS[language];
const taxLabelText = invoiceData.taxLabelText || "VAT";
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

# Check the file structure and context around line 17
cat -n src/app/\(app\)/components/invoice-pdf-template/invoice-vat-summary-table.tsx | head -30

Repository: VladSez/easy-invoice-pdf

Length of output: 1285


🏁 Script executed:

# Search for taxLabelText initialization patterns
rg -n 'taxLabelText' --type=ts --type=tsx -C2

Repository: VladSez/easy-invoice-pdf

Length of output: 95


🏁 Script executed:

# Search for invoiceData type definition
rg -n 'invoiceData\s*[:=]' --type=ts --type=tsx -A5 | head -50

Repository: VladSez/easy-invoice-pdf

Length of output: 95


🏁 Script executed:

# Look for language-related properties in invoice data
rg -n 'language' --type=ts --type=tsx -C2 | head -50

Repository: VladSez/easy-invoice-pdf

Length of output: 95


🏁 Script executed:

# Find and read the translations file
fd translations -type f | head -5

Repository: VladSez/easy-invoice-pdf

Length of output: 238


🏁 Script executed:

# Search for schema definition
fd schema -type f | head -10

Repository: VladSez/easy-invoice-pdf

Length of output: 238


🏁 Script executed:

# Look for how taxLabelText is used in other places
rg 'taxLabelText' --no-require-git -C3

Repository: VladSez/easy-invoice-pdf

Length of output: 20837


🏁 Script executed:

# Check the translations structure
rg 'TRANSLATIONS\s*=' --no-require-git -A20

Repository: VladSez/easy-invoice-pdf

Length of output: 1264


🏁 Script executed:

# Get the full translations file to see all languages
cat src/app/schema/translations.ts | head -100

Repository: VladSez/easy-invoice-pdf

Length of output: 2333


🏁 Script executed:

# Check the schema definition for taxLabelText
cat src/app/schema/index.ts | grep -A10 "taxLabelText"

Repository: VladSez/easy-invoice-pdf

Length of output: 420


🏁 Script executed:

# Check if taxLabelText ever comes undefined or if it always has a default
rg 'taxLabelText' src/app/constants/index.ts -B5 -A5

Repository: VladSez/easy-invoice-pdf

Length of output: 292


🏁 Script executed:

# Get the complete translations file
cat src/app/schema/translations.ts

Repository: VladSez/easy-invoice-pdf

Length of output: 22361


🏁 Script executed:

# Also check how other components handle language defaults
rg 'TRANSLATIONS\[language\]' --no-require-git -B2 -A2

Repository: VladSez/easy-invoice-pdf

Length of output: 8579


Use localized translation fallback for tax label instead of hardcoded "VAT".

The hardcoded fallback "VAT" is in English, but localized alternatives already exist in the t.invoiceItemsTable.vat translations for all supported languages (German: "MwSt", Russian: "НДС", French: "TVA", etc.).

Change line 17 to:

const taxLabelText = invoiceData.taxLabelText || t.invoiceItemsTable.vat;

This pattern also needs to be fixed in:

  • src/app/(app)/components/invoice-pdf-stripe-template/stripe-totals.tsx
  • src/app/(app)/components/invoice-pdf-stripe-template/stripe-items-table.tsx
  • src/app/(app)/components/invoice-pdf-template/invoice-items-table.tsx
  • src/app/(app)/components/invoice-form/index.tsx
🤖 Prompt for AI Agents
In src/app/(app)/components/invoice-pdf-template/invoice-vat-summary-table.tsx
around line 17, the code uses a hardcoded English fallback "VAT" for
taxLabelText; replace that fallback with the localized translation
t.invoiceItemsTable.vat (i.e., set taxLabelText = invoiceData.taxLabelText ||
t.invoiceItemsTable.vat). Apply the same change pattern to the other listed
files so any fallback "VAT" is replaced by t.invoiceItemsTable.vat:
src/app/(app)/components/invoice-pdf-stripe-template/stripe-totals.tsx,
src/app/(app)/components/invoice-pdf-stripe-template/stripe-items-table.tsx,
src/app/(app)/components/invoice-pdf-template/invoice-items-table.tsx, and
src/app/(app)/components/invoice-form/index.tsx.

* Changed import of 'path' to 'node:path' in Playwright configuration for better compatibility.
* Updated GitHub Actions workflow to run e2e tests on macOS for consistent snapshot generation.
* Simplified screenshot assertions in various test files by removing unnecessary path joins.
* Added new snapshot images for various invoice and template tests to ensure visual consistency.
* Removed commented-out code related to tax field visibility in invoice items for cleaner codebase.
* Introduced new type for mobile tab values in schema to enhance type safety.
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

♻️ Duplicate comments (2)
src/app/(app)/components/index.tsx (1)

7-7: Add missing dayjs locale import.

The .locale("en") call on line 124 requires the locale to be explicitly imported. Add import "dayjs/locale/en"; after the dayjs import to ensure the locale is properly loaded.

🔎 Proposed fix
 import dayjs from "dayjs";
+import "dayjs/locale/en";
playwright.config.ts (1)

18-21: Dead code: isLocal condition is unreachable.

The NODE_ENV === "local" check is never true because .env.local doesn't exist in the repository and NODE_ENV is never set to "local" anywhere in the codebase. This makes the local timeout (35s) unreachable, and the @ts-expect-error suppresses a legitimate type issue.

Either document and provide the required .env.local setup, or simplify to a single TIMEOUT value.

🧹 Nitpick comments (6)
src/app/(app)/components/invoice-form/sections/invoice-items.tsx (1)

478-482: Good semantic grouping with fieldset.

The Tax Settings fieldset provides clear organization of tax-related controls. The structure is valid and improves readability.

For future maintainability, consider extracting the Tax Settings section into a separate component (e.g., TaxSettingsSection) to reduce the complexity of this file.

e2e/utils/extended-playwright-test.ts (1)

27-27: Unnecessary ESLint disable: use is not a React Hook.

The react-hooks/rules-of-hooks rule shouldn't apply here—use is a Playwright fixture callback, not a React Hook. The disable comment may be masking a misconfigured ESLint rule or plugin conflict.

Consider removing the disable and verifying your ESLint configuration, or switching to a more specific suppression if the warning persists.

e2e/stripe-invoice-template/template.test.ts (2)

810-810: Flakiness risk: waitForTimeout depends on arbitrary delay.

The 1-second timeout at line 810 (and line 936) assumes PDF rendering completes within that window. On slower CI runners or under load, this could produce intermittent failures.

Consider polling for a specific condition (e.g., PDF canvas ready, page title set) instead of a fixed delay.

🔎 Alternative approach using `waitForLoadState`
-    // sometimes there's a blank screen without this
-    // eslint-disable-next-line playwright/no-wait-for-timeout
-    await page.waitForTimeout(1000);
+    await page.waitForLoadState('networkidle');

806-806: Cross-platform risk: file:// URLs with absolute paths.

Using file://${absolutePath} works on macOS/Linux but can behave unexpectedly on Windows (drive letters, backslashes). Since the CI now runs on macOS, this is acceptable, but document the OS dependency if developers run tests locally on Windows.

e2e/default-invoice-template/pdf.test.ts (2)

29-62: Consider extracting the PDF download-and-verify pattern to a helper function.

This pattern repeats 8+ times throughout the file:

const [download] = await Promise.all([...]);
const suggestedFilename = download.suggestedFilename();
const pdfFilePath = path.join(downloadDir, `${browserName}-...`);
await download.saveAs(pdfFilePath);
// ... file existence check, viewport, navigation, wait, screenshot

Extracting this to a helper would reduce duplication and make future changes easier to maintain.

🔎 Proposed helper function

Add this helper at the top of the file or in a shared utils module:

async function downloadAndVerifyPdf(
  page: Page,
  downloadButton: Locator,
  options: {
    downloadDir: string;
    browserName: string;
    filenamePrefix?: string;
    screenshotName: string;
  }
) {
  const [download] = await Promise.all([
    page.waitForEvent("download"),
    downloadButton.click(),
  ]);

  const suggestedFilename = download.suggestedFilename();
  const prefix = options.filenamePrefix 
    ? `${options.browserName}-${options.filenamePrefix}-` 
    : `${options.browserName}-`;
  const pdfFilePath = path.join(options.downloadDir, `${prefix}${suggestedFilename}`);

  await download.saveAs(pdfFilePath);

  const absolutePath = path.resolve(pdfFilePath);
  await expect.poll(() => fs.existsSync(absolutePath)).toBe(true);

  await page.setViewportSize({ width: 1100, height: 1185 });
  await page.goto(`file://${absolutePath}`);
  
  // eslint-disable-next-line playwright/no-wait-for-timeout
  await page.waitForTimeout(1000);

  await expect(page).toHaveScreenshot(options.screenshotName);
}

Then usage becomes:

await downloadAndVerifyPdf(page, downloadPdfEnglishButton, {
  downloadDir,
  browserName,
  screenshotName: `downloads-PDF-in-English.png`,
});

58-61: Consider more deterministic wait conditions instead of fixed timeout for PDF viewer rendering.

The 1000ms timeout waits for the browser's native PDF viewer to render the PDF file. While the concern about flakiness is valid, for local file:// PDF viewing, more deterministic alternatives may be limited. If investigating improvements, test whether page.waitForLoadState('load') or waiting for a specific element in the PDF viewer UI (e.g., the canvas or toolbar to appear) provides reliable results across browsers. The current pragmatic solution with documentation is acceptable if alternatives prove incompatible with PDF viewer behavior.

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d2a4a9b and 5b4d715.

⛔ Files ignored due to path filters (36)
  • e2e/about-snapshot.test.ts-snapshots/about-page-english-Mobile-Chrome-darwin.png is excluded by !**/*.png
  • e2e/about-snapshot.test.ts-snapshots/about-page-english-Mobile-Safari-darwin.png is excluded by !**/*.png
  • e2e/about-snapshot.test.ts-snapshots/about-page-english-chromium-darwin.png is excluded by !**/*.png
  • e2e/default-invoice-template/pdf.test.ts-snapshots/completes-full-invoice-flow-on-mobile-Mobile-Chrome-darwin.png is excluded by !**/*.png
  • e2e/default-invoice-template/pdf.test.ts-snapshots/completes-full-invoice-flow-on-mobile-Mobile-Safari-darwin.png is excluded by !**/*.png
  • e2e/default-invoice-template/pdf.test.ts-snapshots/completes-full-invoice-flow-on-mobile-chromium-darwin.png is excluded by !**/*.png
  • e2e/default-invoice-template/pdf.test.ts-snapshots/completes-full-invoice-flow-on-mobile-stripe-template-Mobile-Chrome-darwin.png is excluded by !**/*.png
  • e2e/default-invoice-template/pdf.test.ts-snapshots/completes-full-invoice-flow-on-mobile-stripe-template-Mobile-Safari-darwin.png is excluded by !**/*.png
  • e2e/default-invoice-template/pdf.test.ts-snapshots/completes-full-invoice-flow-on-mobile-stripe-template-chromium-darwin.png is excluded by !**/*.png
  • e2e/default-invoice-template/pdf.test.ts-snapshots/downloads-PDF-in-English-Mobile-Chrome-darwin.png is excluded by !**/*.png
  • e2e/default-invoice-template/pdf.test.ts-snapshots/downloads-PDF-in-English-Mobile-Safari-darwin.png is excluded by !**/*.png
  • e2e/default-invoice-template/pdf.test.ts-snapshots/downloads-PDF-in-English-chromium-darwin.png is excluded by !**/*.png
  • e2e/default-invoice-template/pdf.test.ts-snapshots/downloads-PDF-in-Polish-Mobile-Chrome-darwin.png is excluded by !**/*.png
  • e2e/default-invoice-template/pdf.test.ts-snapshots/downloads-PDF-in-Polish-Mobile-Safari-darwin.png is excluded by !**/*.png
  • e2e/default-invoice-template/pdf.test.ts-snapshots/downloads-PDF-in-Polish-chromium-darwin.png is excluded by !**/*.png
  • e2e/default-invoice-template/pdf.test.ts-snapshots/downloads-PDF-in-Polish-stripe-template-Mobile-Chrome-darwin.png is excluded by !**/*.png
  • e2e/default-invoice-template/pdf.test.ts-snapshots/downloads-PDF-in-Polish-stripe-template-Mobile-Safari-darwin.png is excluded by !**/*.png
  • e2e/default-invoice-template/pdf.test.ts-snapshots/downloads-PDF-in-Polish-stripe-template-chromium-darwin.png is excluded by !**/*.png
  • e2e/default-invoice-template/pdf.test.ts-snapshots/should-display-and-persist-invoice-number-in-different-languages-Mobile-Chrome-darwin.png is excluded by !**/*.png
  • e2e/default-invoice-template/pdf.test.ts-snapshots/should-display-and-persist-invoice-number-in-different-languages-Mobile-Safari-darwin.png is excluded by !**/*.png
  • e2e/default-invoice-template/pdf.test.ts-snapshots/should-display-and-persist-invoice-number-in-different-languages-chromium-darwin.png is excluded by !**/*.png
  • e2e/default-invoice-template/pdf.test.ts-snapshots/should-display-and-persist-invoice-number-in-different-languages-stripe-template-Mobile-Chrome-darwin.png is excluded by !**/*.png
  • e2e/default-invoice-template/pdf.test.ts-snapshots/should-display-and-persist-invoice-number-in-different-languages-stripe-template-Mobile-Safari-darwin.png is excluded by !**/*.png
  • e2e/default-invoice-template/pdf.test.ts-snapshots/should-display-and-persist-invoice-number-in-different-languages-stripe-template-chromium-darwin.png is excluded by !**/*.png
  • e2e/default-invoice-template/pdf.test.ts-snapshots/update-pdf-when-invoice-data-changes-Mobile-Chrome-darwin.png is excluded by !**/*.png
  • e2e/default-invoice-template/pdf.test.ts-snapshots/update-pdf-when-invoice-data-changes-Mobile-Safari-darwin.png is excluded by !**/*.png
  • e2e/default-invoice-template/pdf.test.ts-snapshots/update-pdf-when-invoice-data-changes-chromium-darwin.png is excluded by !**/*.png
  • e2e/default-invoice-template/pdf.test.ts-snapshots/update-pdf-when-invoice-data-changes-stripe-template-Mobile-Chrome-darwin.png is excluded by !**/*.png
  • e2e/default-invoice-template/pdf.test.ts-snapshots/update-pdf-when-invoice-data-changes-stripe-template-Mobile-Safari-darwin.png is excluded by !**/*.png
  • e2e/default-invoice-template/pdf.test.ts-snapshots/update-pdf-when-invoice-data-changes-stripe-template-chromium-darwin.png is excluded by !**/*.png
  • e2e/stripe-invoice-template/template.test.ts-snapshots/automatically-enables-VAT-field-visibility-and-sets-date-format-when-switching-to-Stripe-template-Mobile-Chrome-darwin.png is excluded by !**/*.png
  • e2e/stripe-invoice-template/template.test.ts-snapshots/automatically-enables-VAT-field-visibility-and-sets-date-format-when-switching-to-Stripe-template-Mobile-Safari-darwin.png is excluded by !**/*.png
  • e2e/stripe-invoice-template/template.test.ts-snapshots/automatically-enables-VAT-field-visibility-and-sets-date-format-when-switching-to-Stripe-template-chromium-darwin.png is excluded by !**/*.png
  • e2e/stripe-invoice-template/template.test.ts-snapshots/pdf-with-logo-and-payment-url-when-using-stripe-template-Mobile-Chrome-darwin.png is excluded by !**/*.png
  • e2e/stripe-invoice-template/template.test.ts-snapshots/pdf-with-logo-and-payment-url-when-using-stripe-template-Mobile-Safari-darwin.png is excluded by !**/*.png
  • e2e/stripe-invoice-template/template.test.ts-snapshots/pdf-with-logo-and-payment-url-when-using-stripe-template-chromium-darwin.png is excluded by !**/*.png
📒 Files selected for processing (9)
  • .github/workflows/e2e.yml
  • e2e/about-snapshot.test.ts
  • e2e/default-invoice-template/pdf.test.ts
  • e2e/stripe-invoice-template/template.test.ts
  • e2e/utils/extended-playwright-test.ts
  • playwright.config.ts
  • src/app/(app)/components/index.tsx
  • src/app/(app)/components/invoice-form/sections/invoice-items.tsx
  • src/app/schema/index.ts
🧰 Additional context used
🧠 Learnings (14)
📚 Learning: 2025-07-20T22:00:37.696Z
Learnt from: CR
Repo: VladSez/easy-invoice-pdf PR: 0
File: .cursor/rules/testing.mdc:0-0
Timestamp: 2025-07-20T22:00:37.696Z
Learning: Use Playwright for E2E testing

Applied to files:

  • .github/workflows/e2e.yml
  • playwright.config.ts
  • e2e/default-invoice-template/pdf.test.ts
  • e2e/utils/extended-playwright-test.ts
  • e2e/about-snapshot.test.ts
📚 Learning: 2025-07-20T22:00:19.380Z
Learnt from: CR
Repo: VladSez/easy-invoice-pdf PR: 0
File: .cursor/rules/shared.mdc:0-0
Timestamp: 2025-07-20T22:00:19.380Z
Learning: Applies to **/*.tsx : Implement responsive design with Tailwind CSS; use a mobile-first approach.

Applied to files:

  • src/app/(app)/components/index.tsx
📚 Learning: 2025-07-20T22:00:37.696Z
Learnt from: CR
Repo: VladSez/easy-invoice-pdf PR: 0
File: .cursor/rules/testing.mdc:0-0
Timestamp: 2025-07-20T22:00:37.696Z
Learning: Applies to **/*.test.{ts,tsx} : Keep tests isolated

Applied to files:

  • playwright.config.ts
  • e2e/utils/extended-playwright-test.ts
📚 Learning: 2025-07-20T22:00:37.696Z
Learnt from: CR
Repo: VladSez/easy-invoice-pdf PR: 0
File: .cursor/rules/testing.mdc:0-0
Timestamp: 2025-07-20T22:00:37.696Z
Learning: Applies to **/*.test.{ts,tsx} : Use MSW for API mocking in tests

Applied to files:

  • playwright.config.ts
  • e2e/utils/extended-playwright-test.ts
  • e2e/stripe-invoice-template/template.test.ts
📚 Learning: 2025-07-20T22:00:37.696Z
Learnt from: CR
Repo: VladSez/easy-invoice-pdf PR: 0
File: .cursor/rules/testing.mdc:0-0
Timestamp: 2025-07-20T22:00:37.696Z
Learning: Applies to **/*.test.{ts,tsx} : Mock external dependencies in tests

Applied to files:

  • playwright.config.ts
  • e2e/utils/extended-playwright-test.ts
  • e2e/stripe-invoice-template/template.test.ts
📚 Learning: 2025-07-20T21:59:36.257Z
Learnt from: CR
Repo: VladSez/easy-invoice-pdf PR: 0
File: .cursor/rules/error-handling.mdc:0-0
Timestamp: 2025-07-20T21:59:36.257Z
Learning: Applies to **/*.{ts,tsx} : Define reusable validation schemas

Applied to files:

  • src/app/schema/index.ts
📚 Learning: 2025-07-20T21:59:36.257Z
Learnt from: CR
Repo: VladSez/easy-invoice-pdf PR: 0
File: .cursor/rules/error-handling.mdc:0-0
Timestamp: 2025-07-20T21:59:36.257Z
Learning: Applies to **/*.{ts,tsx} : Use Zod schemas and z.infer for form validation and type inference in TypeScript

Applied to files:

  • src/app/schema/index.ts
📚 Learning: 2025-07-20T22:00:37.696Z
Learnt from: CR
Repo: VladSez/easy-invoice-pdf PR: 0
File: .cursor/rules/testing.mdc:0-0
Timestamp: 2025-07-20T22:00:37.696Z
Learning: Applies to **/*.test.{ts,tsx} : Integration tests must cover key user flows

Applied to files:

  • e2e/default-invoice-template/pdf.test.ts
  • e2e/stripe-invoice-template/template.test.ts
  • e2e/about-snapshot.test.ts
📚 Learning: 2025-07-20T22:00:37.696Z
Learnt from: CR
Repo: VladSez/easy-invoice-pdf PR: 0
File: .cursor/rules/testing.mdc:0-0
Timestamp: 2025-07-20T22:00:37.696Z
Learning: Applies to **/*.test.{ts,tsx} : Use meaningful assertions in tests

Applied to files:

  • e2e/default-invoice-template/pdf.test.ts
  • e2e/utils/extended-playwright-test.ts
  • e2e/stripe-invoice-template/template.test.ts
  • e2e/about-snapshot.test.ts
📚 Learning: 2025-07-20T22:00:37.696Z
Learnt from: CR
Repo: VladSez/easy-invoice-pdf PR: 0
File: .cursor/rules/testing.mdc:0-0
Timestamp: 2025-07-20T22:00:37.696Z
Learning: Applies to **/*.test.{ts,tsx} : Follow AAA pattern (Arrange, Act, Assert) in tests

Applied to files:

  • e2e/default-invoice-template/pdf.test.ts
  • e2e/utils/extended-playwright-test.ts
  • e2e/stripe-invoice-template/template.test.ts
  • e2e/about-snapshot.test.ts
📚 Learning: 2025-07-20T22:00:37.696Z
Learnt from: CR
Repo: VladSez/easy-invoice-pdf PR: 0
File: .cursor/rules/testing.mdc:0-0
Timestamp: 2025-07-20T22:00:37.696Z
Learning: Applies to **/*.test.{ts,tsx} : Use descriptive test names

Applied to files:

  • e2e/default-invoice-template/pdf.test.ts
  • e2e/utils/extended-playwright-test.ts
  • e2e/stripe-invoice-template/template.test.ts
  • e2e/about-snapshot.test.ts
📚 Learning: 2025-07-20T22:00:37.696Z
Learnt from: CR
Repo: VladSez/easy-invoice-pdf PR: 0
File: .cursor/rules/testing.mdc:0-0
Timestamp: 2025-07-20T22:00:37.696Z
Learning: Applies to **/*.test.{ts,tsx} : Test error cases

Applied to files:

  • e2e/default-invoice-template/pdf.test.ts
  • e2e/utils/extended-playwright-test.ts
  • e2e/stripe-invoice-template/template.test.ts
📚 Learning: 2025-07-20T22:00:37.696Z
Learnt from: CR
Repo: VladSez/easy-invoice-pdf PR: 0
File: .cursor/rules/testing.mdc:0-0
Timestamp: 2025-07-20T22:00:37.696Z
Learning: Applies to **/*.test.{ts,tsx} : Use test data factories in tests

Applied to files:

  • e2e/utils/extended-playwright-test.ts
  • e2e/stripe-invoice-template/template.test.ts
📚 Learning: 2025-07-20T22:00:37.696Z
Learnt from: CR
Repo: VladSez/easy-invoice-pdf PR: 0
File: .cursor/rules/testing.mdc:0-0
Timestamp: 2025-07-20T22:00:37.696Z
Learning: Applies to **/*.test.{ts,tsx} : Test cases should use it('should do something specific', () => {})

Applied to files:

  • e2e/utils/extended-playwright-test.ts
  • e2e/about-snapshot.test.ts
🧬 Code graph analysis (4)
src/app/(app)/components/index.tsx (2)
src/app/schema/index.ts (3)
  • MOBILE_TABS_VALUES (775-775)
  • DEFAULT_MOBILE_TAB (776-776)
  • MobileTabsValues (778-778)
src/app/(app)/utils/get-app-metadata.tsx (2)
  • getAppMetadata (30-58)
  • updateAppMetadata (81-116)
e2e/default-invoice-template/pdf.test.ts (1)
e2e/utils/extended-playwright-test.ts (1)
  • test (17-33)
src/app/(app)/components/invoice-form/sections/invoice-items.tsx (4)
src/components/ui/label.tsx (1)
  • Label (24-24)
src/components/ui/input.tsx (1)
  • Input (29-29)
src/app/(app)/components/invoice-form/common.tsx (1)
  • ErrorMessage (3-5)
src/components/ui/input-helper-message.tsx (1)
  • InputHelperMessage (3-17)
e2e/about-snapshot.test.ts (1)
e2e/utils/extended-playwright-test.ts (1)
  • test (17-33)
🔇 Additional comments (31)
src/app/schema/index.ts (7)

341-352: LGTM! Version constants added.

The APP_VERSION and SCHEMA_VERSION constants are well-documented and provide a foundation for version management and future schema migrations.


405-459: Well-designed flexible VAT validation.

The preprocessing approach elegantly handles both numeric (0-100) and text-based VAT values (e.g., "NP", "OO"). The logic correctly trims input, handles null/undefined, and attempts numeric conversion before falling back to string validation.


478-531: LGTM! Seller schema refactored with customizable tax label.

The refactoring to z.object() structure is clean, and the new vatNoLabelText field with sensible defaults and validation enables customizable tax labels as intended.


535-572: LGTM! Buyer schema refactored consistently.

The buyer schema changes mirror the seller schema refactoring, maintaining consistency across entity types.


634-648: LGTM! Tax label customization added.

The taxLabelText field provides the intended flexibility for international tax terminology (VAT, GST, IVA, Sales Tax, etc.) with appropriate validation and a sensible default.


775-778: LGTM! Mobile tab constants defined.

The mobile tab values and default are clearly defined with proper type inference.


780-807: LGTM! Metadata schema well-structured.

The metadata schema appropriately captures app versioning, timestamps, and user state (mobile tab) with sensible defaults and validation.

src/app/(app)/components/index.tsx (8)

98-99: LGTM! Tab constants properly derived.

The tab constants correctly reference the centralized MOBILE_TABS_VALUES array, improving maintainability.


120-129: LGTM! Metadata retrieval and formatting handled correctly.

The code properly retrieves app metadata, safely formats the timestamp with null checks, and provides a sensible fallback for the mobile tab default.


135-148: LGTM! Mobile tab persistence correctly implemented.

The tab component properly initializes from metadata and persists the last visited tab. The type assertion to MobileTabsValues on line 139 correctly uses the union type (addressing the previous review concern).


165-165: LGTM! Mobile container heights increased.

The height increase from 480px to 520px provides more viewing space for both form and preview on mobile devices.

Also applies to: 174-174


240-245: LGTM! Mobile timestamp display implemented correctly.

The timestamp display includes proper null checks and smooth fade-in animations.


246-258: LGTM! Mobile credits section added.

The credits are properly implemented with secure external link handling (noopener noreferrer).


272-282: LGTM! Desktop credits section added.

The credits section mirrors the mobile implementation with consistent styling and secure link attributes.


284-290: LGTM! Desktop timestamp display positioned correctly.

The timestamp is properly positioned using absolute positioning with the parent container set to relative (line 284), ensuring correct layout.

src/app/(app)/components/invoice-form/sections/invoice-items.tsx (3)

44-44: LGTM! Clean API extension.

The addition of taxLabelText to the component's props is well-typed and integrates cleanly with the existing interface.

Also applies to: 56-56


84-88: LGTM! Proper use of dynamic tax label.

The label correctly interpolates taxLabelText to display customizable tax terminology.


505-505: LGTM! Consistent dynamic tax label usage.

All labels, tooltips, and helper messages correctly use the dynamic taxLabelText prop, enabling proper customization of tax terminology throughout the UI.

Also applies to: 539-539, 641-641, 668-668, 700-702, 768-770

.github/workflows/e2e.yml (1)

12-13: macOS runner choice is well-justified.

The switch to macos-latest aligns with local snapshot generation and prevents cross-platform pixel differences. This is the correct approach for maintaining consistent visual regression tests.

Note: macOS runners incur higher costs than Linux, but the reliability benefit outweighs the expense for snapshot testing.

playwright.config.ts (1)

66-66: LGTM: Explicit Chromium channel improves stability.

Specifying channel: "chromium" ensures tests run against the bundled Chromium rather than any installed Chrome/Edge, reducing environment variability.

e2e/about-snapshot.test.ts (1)

15-21: Excellent: Video state normalization for snapshot stability.

Pausing and resetting video playback ensures deterministic snapshots by eliminating frame-dependent visual differences. This is a best practice for visual regression testing.

e2e/utils/extended-playwright-test.ts (1)

20-32: Solid isolation pattern with automatic cleanup.

The fixture correctly creates a unique temp directory per test and guarantees cleanup via the finally block, preventing disk space leaks even when tests fail.

e2e/stripe-invoice-template/template.test.ts (2)

16-17: Good: Fixed system time ensures deterministic test data.

Setting a fixed date via page.clock.setSystemTime() stabilizes invoice numbers, date formatting, and any time-dependent logic across test runs.


852-941: Comprehensive end-to-end coverage for Stripe template with assets.

This test validates the complete flow: logo upload, payment URL entry, localStorage persistence, PDF generation, and visual verification. The screenshot-based approach ensures the PDF rendering matches expectations.

e2e/default-invoice-template/pdf.test.ts (7)

1-7: LGTM on imports and fixture usage!

The switch to the custom extended Playwright fixture is a good pattern that provides isolated temporary download directories per test, preventing cross-test pollution. The fixture handles cleanup automatically in its finally block. Based on learnings, this aligns with proper Playwright e2e testing practices.


9-15: Good use of fixed system time for deterministic tests.

Setting page.clock.setSystemTime ensures invoice numbers and dates remain consistent across test runs, which is essential for screenshot-based verification.


65-128: Good test coverage for form interactions and calculations.

The test properly verifies:

  • Language switching to Polish
  • Invoice item manipulation (name, quantity, price, VAT)
  • Adding a new invoice item
  • Total calculation verification (3,300.00)

This follows the AAA pattern and includes meaningful assertions as per testing guidelines.


171-180: Template switching verification is thorough.

Good practice to verify both the URL change (waitForURL) and the select element value after switching templates.


443-453: Excellent mobile-specific test coverage.

This test properly covers:

  • Mobile viewport simulation
  • Tab-based navigation (Edit/Preview)
  • Form data persistence across navigation
  • French localization

This aligns with the learning that integration tests must cover key user flows.


581-586: Good practice: restoring mobile viewport after PDF verification.

The viewport reset to mobile dimensions after navigating back ensures the subsequent mobile-specific assertions (tab navigation, form persistence) work correctly.


707-764: Thorough language and persistence testing.

The test covers important scenarios:

  • Invoice number label translation per language
  • "Switch to default format" warning UI
  • Data persistence after page reload
  • Currency persistence across template changes

Using TRANSLATIONS.pl.invoiceNumber for expected values ensures tests stay in sync with the actual translations.

checked={value}
onCheckedChange={onChange}
className="h-5 w-8 [&_span]:size-4 [&_span]:data-[state=checked]:translate-x-3 rtl:[&_span]:data-[state=checked]:-translate-x-3"
// aria-label={`Show/hide the 'Name of Goods/Service' Column in the PDF for item ${index + 1}`}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Remove commented-out aria-label attributes.

These commented-out attributes are remnants from refactoring and should be deleted. Version control preserves history if they're needed later.

🔎 Proposed cleanup
                            className="h-5 w-8 [&_span]:size-4 [&_span]:data-[state=checked]:translate-x-3 rtl:[&_span]:data-[state=checked]:-translate-x-3"
-                            // aria-label={`Show/hide the 'Name of Goods/Service' Column in the PDF for item ${index + 1}`}
                          />
                            className="h-5 w-8 [&_span]:size-4 [&_span]:data-[state=checked]:translate-x-3 rtl:[&_span]:data-[state=checked]:-translate-x-3"
-                            // aria-label={`Show/hide the 'Type of GTU' Column in the PDF for item ${index + 1}`}
                          />
                            className="h-5 w-8 [&_span]:size-4 [&_span]:data-[state=checked]:translate-x-3 rtl:[&_span]:data-[state=checked]:-translate-x-3"
-                            // aria-label={`Show/hide the 'Amount' Column in the PDF for item ${index + 1}`}
                          />

Also applies to: 224-224, 283-283

🤖 Prompt for AI Agents
In src/app/(app)/components/invoice-form/sections/invoice-items.tsx around lines
165, 224, and 283 there are commented-out aria-label attributes left from
refactoring; remove those commented lines entirely (delete the commented
aria-label snippets at those exact line locations) so the file contains no
leftover commented accessibility attributes—rely on VCS history if the old lines
are needed later.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

♻️ Duplicate comments (1)
e2e/utils/extended-playwright-test.ts (1)

20-20: Replace empty object pattern with underscore.

The first parameter uses an empty object pattern {} which is flagged by the linter. Replace it with _ to indicate the parameter is intentionally unused.

🔎 Proposed fix
-  downloadDir: async ({}, use) => {
+  downloadDir: async (_, use) => {
🧹 Nitpick comments (1)
e2e/utils/extended-playwright-test.ts (1)

27-28: Remove inappropriate eslint-disable comment.

The use callback is a Playwright fixture function, not a React hook. The react-hooks/rules-of-hooks disable comment is misleading and unnecessary.

🔎 Proposed fix
    try {
      // Make the directory path available to the test
-      // eslint-disable-next-line react-hooks/rules-of-hooks
      await use(downloadDir);
    } finally {
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5b4d715 and d960b4d.

📒 Files selected for processing (1)
  • e2e/utils/extended-playwright-test.ts
🧰 Additional context used
🧠 Learnings (11)
📚 Learning: 2025-07-20T22:00:37.696Z
Learnt from: CR
Repo: VladSez/easy-invoice-pdf PR: 0
File: .cursor/rules/testing.mdc:0-0
Timestamp: 2025-07-20T22:00:37.696Z
Learning: Use Playwright for E2E testing

Applied to files:

  • e2e/utils/extended-playwright-test.ts
📚 Learning: 2025-07-20T22:00:37.696Z
Learnt from: CR
Repo: VladSez/easy-invoice-pdf PR: 0
File: .cursor/rules/testing.mdc:0-0
Timestamp: 2025-07-20T22:00:37.696Z
Learning: Applies to **/*.test.{ts,tsx} : Keep tests isolated

Applied to files:

  • e2e/utils/extended-playwright-test.ts
📚 Learning: 2025-07-20T22:00:37.696Z
Learnt from: CR
Repo: VladSez/easy-invoice-pdf PR: 0
File: .cursor/rules/testing.mdc:0-0
Timestamp: 2025-07-20T22:00:37.696Z
Learning: Applies to **/*.test.{ts,tsx} : Use descriptive test names

Applied to files:

  • e2e/utils/extended-playwright-test.ts
📚 Learning: 2025-07-20T22:00:37.696Z
Learnt from: CR
Repo: VladSez/easy-invoice-pdf PR: 0
File: .cursor/rules/testing.mdc:0-0
Timestamp: 2025-07-20T22:00:37.696Z
Learning: Applies to **/*.test.{ts,tsx} : Mock external dependencies in tests

Applied to files:

  • e2e/utils/extended-playwright-test.ts
📚 Learning: 2025-07-20T22:00:37.696Z
Learnt from: CR
Repo: VladSez/easy-invoice-pdf PR: 0
File: .cursor/rules/testing.mdc:0-0
Timestamp: 2025-07-20T22:00:37.696Z
Learning: Applies to **/*.test.{ts,tsx} : Use meaningful assertions in tests

Applied to files:

  • e2e/utils/extended-playwright-test.ts
📚 Learning: 2025-07-20T22:00:37.696Z
Learnt from: CR
Repo: VladSez/easy-invoice-pdf PR: 0
File: .cursor/rules/testing.mdc:0-0
Timestamp: 2025-07-20T22:00:37.696Z
Learning: Applies to **/*.test.{ts,tsx} : Use test data factories in tests

Applied to files:

  • e2e/utils/extended-playwright-test.ts
📚 Learning: 2025-07-20T22:00:37.696Z
Learnt from: CR
Repo: VladSez/easy-invoice-pdf PR: 0
File: .cursor/rules/testing.mdc:0-0
Timestamp: 2025-07-20T22:00:37.696Z
Learning: Applies to **/*.test.{ts,tsx} : Use MSW for API mocking in tests

Applied to files:

  • e2e/utils/extended-playwright-test.ts
📚 Learning: 2025-07-20T22:00:37.696Z
Learnt from: CR
Repo: VladSez/easy-invoice-pdf PR: 0
File: .cursor/rules/testing.mdc:0-0
Timestamp: 2025-07-20T22:00:37.696Z
Learning: Applies to **/*.test.{ts,tsx} : Follow AAA pattern (Arrange, Act, Assert) in tests

Applied to files:

  • e2e/utils/extended-playwright-test.ts
📚 Learning: 2025-07-20T22:00:37.696Z
Learnt from: CR
Repo: VladSez/easy-invoice-pdf PR: 0
File: .cursor/rules/testing.mdc:0-0
Timestamp: 2025-07-20T22:00:37.696Z
Learning: Applies to **/*.test.{ts,tsx} : Test error cases

Applied to files:

  • e2e/utils/extended-playwright-test.ts
📚 Learning: 2025-07-20T22:00:37.696Z
Learnt from: CR
Repo: VladSez/easy-invoice-pdf PR: 0
File: .cursor/rules/testing.mdc:0-0
Timestamp: 2025-07-20T22:00:37.696Z
Learning: Applies to **/*.test.{ts,tsx} : Integration tests must cover key user flows

Applied to files:

  • e2e/utils/extended-playwright-test.ts
📚 Learning: 2025-07-20T22:00:37.696Z
Learnt from: CR
Repo: VladSez/easy-invoice-pdf PR: 0
File: .cursor/rules/testing.mdc:0-0
Timestamp: 2025-07-20T22:00:37.696Z
Learning: Applies to **/*.test.{ts,tsx} : Test cases should use it('should do something specific', () => {})

Applied to files:

  • e2e/utils/extended-playwright-test.ts
🪛 Biome (2.1.2)
e2e/utils/extended-playwright-test.ts

[error] 20-20: Unexpected empty object pattern.

(lint/correctness/noEmptyPattern)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Run e2e tests via Vercel deployment
🔇 Additional comments (2)
e2e/utils/extended-playwright-test.ts (2)

1-4: LGTM! Clean imports with best practices.

The imports are well-organized, using the node: prefix for built-in modules as recommended.


17-33: Solid fixture implementation following Playwright best practices.

The downloadDir fixture correctly:

  • Creates an isolated temporary directory per test
  • Guarantees cleanup via try/finally block
  • Uses appropriate rmSync options (recursive, force) for reliable cleanup

The export naming convention is also correctly preserved for ESLint plugin compatibility.

* Modified dotenv configuration to load environment variables quietly.
* Updated GitHub Actions workflow to use macOS 14 for improved compatibility with Playwright snapshot generation.
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d960b4d and 8b6079c.

📒 Files selected for processing (2)
  • .github/workflows/e2e.yml
  • playwright.config.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • playwright.config.ts
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-07-20T22:00:37.696Z
Learnt from: CR
Repo: VladSez/easy-invoice-pdf PR: 0
File: .cursor/rules/testing.mdc:0-0
Timestamp: 2025-07-20T22:00:37.696Z
Learning: Use Playwright for E2E testing

Applied to files:

  • .github/workflows/e2e.yml

Comment thread .github/workflows/e2e.yml Outdated
Comment on lines +12 to +13
# IMPORTANT: we use "macos-14" due to the playwright snapshot updates issues on mac os vs linux (locally I use macos to generate snapshots and it's required to have the same OS for the CI)
runs-on: macos-14
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

cat -n .github/workflows/e2e.yml

Repository: VladSez/easy-invoice-pdf

Length of output: 5415


🏁 Script executed:

fd playwright.config -type f

Repository: VladSez/easy-invoice-pdf

Length of output: 238


🏁 Script executed:

fd "\.e2e\.|\.spec\." -type f | head -20

Repository: VladSez/easy-invoice-pdf

Length of output: 238


🌐 Web query:

Playwright cross-platform screenshot testing best practices 2025

💡 Result:

Here are concise, current best practices for cross‑platform Playwright screenshot/visual testing (2025):

  1. Run screenshots in a consistent environment (same OS, browser version, headless/headful mode, GPU, scaling). Baselines must be generated where CI will run. [1][2]

  2. Scope screenshots to stable elements when possible (locator.screenshot) instead of full‑page captures to reduce noise. [1][6]

  3. Mask or remove dynamic content (timestamps, ads, user data) and freeze animations/transitions before capture (CSS injection or hide selectors). [2][3]

  4. Use stable, semantic locators (getByRole, data-testid) to reduce flakiness in element selection. [4]

  5. Tune comparison tolerance: set maxDiffPixels / thresholds and per‑test options rather than brittle exact matches. [1][5]

  6. Version‑control snapshot files and review updates carefully; update baselines only after intentional UI changes (use --update-snapshots). [1][6]

  7. Run visual tests on CI or real device/cloud providers for cross‑OS/browser parity (BrowserStack/Checkly/etc.) rather than developer laptops. [4][5]

  8. Isolate tests (fresh browser context) and fix viewport/resolution to target breakpoints you care about (mobile/desktop). [1][4]

  9. Use Playwright tooling for debugging failing visual tests: trace viewer, HTML reports, automatic screenshots on error. [0][1]

  10. Start small and scale: cover critical user journeys/components first; automate review/approval workflow for snapshot changes. [3][4]

Sources: Playwright docs — Visual comparisons & Best Practices; BrowserStack Playwright guide; Checkly visual regression docs.
[1] Playwright — Visual comparisons / snapshots.
[0] Playwright — Best Practices.
[4] BrowserStack Playwright best practices.
[5] Checkly — Visual regression / Playwright docs.


🏁 Script executed:

find . -name "playwright.config.*" -type f

Repository: VladSez/easy-invoice-pdf

Length of output: 90


🏁 Script executed:

rg "toMatchSnapshot|toHaveScreenshot|screenshot\(" --max-count 20 -n

Repository: VladSez/easy-invoice-pdf

Length of output: 1180


🏁 Script executed:

ls -la | grep -i playwright

Repository: VladSez/easy-invoice-pdf

Length of output: 140


🏁 Script executed:

cat -n playwright.config.ts

Repository: VladSez/easy-invoice-pdf

Length of output: 5506


🏁 Script executed:

rg "updateSnapshots|update|platform|mac|linux" playwright.config.ts -n

Repository: VladSez/easy-invoice-pdf

Length of output: 50


🏁 Script executed:

find . -name "*snapshot*" -o -name "*.png" | grep -E "(e2e|__screenshots__|test-results)" | head -20

Repository: VladSez/easy-invoice-pdf

Length of output: 2391


🏁 Script executed:

cat .gitignore | grep -i snapshot

Repository: VladSez/easy-invoice-pdf

Length of output: 50


🏁 Script executed:

fd "CONTRIBUTING\|README" -type f | head -5

Repository: VladSez/easy-invoice-pdf

Length of output: 238


🏁 Script executed:

ls -la e2e/default-invoice-template/pdf.test.ts-snapshots/ | head -10

Repository: VladSez/easy-invoice-pdf

Length of output: 1008


🏁 Script executed:

git log --oneline -n 5 -- "e2e/*snapshot*" 2>/dev/null || echo "Git history check"

Repository: VladSez/easy-invoice-pdf

Length of output: 142


🏁 Script executed:

rg "snapshotPathTemplate|updateSnapshots" . --type ts --type js 2>/dev/null || echo "No custom snapshot config found"

Repository: VladSez/easy-invoice-pdf

Length of output: 99


Restructure snapshot generation to avoid forcing macOS-only workflow.

The current setup generates snapshots locally on macOS and commits them to git (all snapshots end with -darwin.png), then runs CI on macos-14 to match those baselines. This design forces all developers to use macOS for snapshot updates and increases CI costs by ~10x.

Instead, follow Playwright's 2025 best practice: generate baselines where CI will run, not on developer laptops. Recommended approach:

  1. Update playwright.config.ts to add snapshotPathTemplate configuration for CI snapshot generation
  2. Switch CI runner to ubuntu-latest (or your preferred platform)
  3. Use playwright test --update-snapshots in a dedicated CI workflow step to generate/update baseline snapshots
  4. Developers run tests locally to verify (snapshots auto-update during CI runs when intentional changes occur)

This eliminates the macOS requirement for developers while maintaining snapshot consistency and reducing CI costs significantly.

Alternatively, consider element-level screenshots with tolerance thresholds (await locator.screenshot() + maxDiffPixels) or a visual regression service for cross-platform testing.

🤖 Prompt for AI Agents
.github/workflows/e2e.yml lines 12-13: the workflow currently pins runs-on to
macos-14 to match locally generated Playwright snapshots; instead, change the
runner to ubuntu-latest and add a dedicated CI step that runs Playwright with
snapshot updates so baselines are generated where CI runs. Update
playwright.config.ts to set snapshotPathTemplate so CI-produced snapshots have
stable paths on Linux, add a workflow step to run `playwright test
--update-snapshots` (or equivalent) prior to the test step and commit or persist
the generated baselines as artifacts or in a branch/PR flow, and remove the
macOS requirement to allow developers on any OS and reduce CI cost.

* Changed the GitHub Actions workflow to use macOS-latest for improved compatibility with Playwright.
* Updated screenshot assertions in various test files to include maxDiffPixelRatio for better visual comparison.
* Enhanced screenshot handling in tests for the About page and Default Invoice Template to ensure consistency.
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

♻️ Duplicate comments (1)
e2e/stripe-invoice-template/template.test.ts (1)

613-625: Incomplete test coverage: Field visibility assertions still commented out.

This TODO block with disabled assertions was flagged in a previous review. Tracking this as a known gap in test coverage.

🧹 Nitpick comments (4)
e2e/default-invoice-template/pdf.test.ts (3)

58-64: Consider extracting repeated PDF download-and-verify pattern into a helper.

The pattern of downloading PDF → saving to temp dir → navigating to file:// URL → waiting 1000ms → taking screenshot is repeated 9+ times across this file. Extracting this into a helper function would reduce duplication and make maintenance easier.

🔎 Example helper extraction
// In e2e/utils/pdf-helpers.ts
export async function downloadAndVerifyPdf(
  page: Page,
  downloadButton: Locator,
  downloadDir: string,
  browserName: string,
  screenshotName: string,
  filePrefix = ''
) {
  const [download] = await Promise.all([
    page.waitForEvent("download"),
    downloadButton.click(),
  ]);

  const suggestedFilename = download.suggestedFilename();
  const pdfFilePath = path.join(
    downloadDir,
    `${browserName}-${filePrefix}${suggestedFilename}`
  );

  await download.saveAs(pdfFilePath);
  const absolutePath = path.resolve(pdfFilePath);
  await expect.poll(() => fs.existsSync(absolutePath)).toBe(true);

  await page.setViewportSize({ width: 1100, height: 1185 });
  await page.goto(`file://${absolutePath}`);
  await page.waitForTimeout(1000);

  await expect(page).toHaveScreenshot(screenshotName, {
    maxDiffPixelRatio: 0.01,
  });
}

58-61: Fixed timeout for PDF rendering may cause flakiness.

The 1000ms waitForTimeout before taking screenshots is a potential flakiness source. Consider waiting for a more deterministic condition, such as the PDF viewer's loading indicator to disappear, a specific element within the PDF viewer, or using page.waitForLoadState('networkidle').


170-232: Consider reorganizing Stripe template verification into the Stripe test file.

The Default Invoice Template test file contains significant Stripe template verification blocks (lines 170-232, 392-453, 665-723, 868-933). While testing template switching within a flow is valid, consider whether these could be better organized in e2e/stripe-invoice-template/template.test.ts to improve test discoverability and maintainability.

e2e/stripe-invoice-template/template.test.ts (1)

898-900: Remove commented-out code.

Line 899 contains a commented-out downloadPromise variable that's no longer used. Clean up this dead code.

🔎 Proposed fix
-    // Set up download handler
-    // const downloadPromise = page.waitForEvent("download");
-
     const downloadPDFButton = page.getByRole("link", {
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8b6079c and 5575343.

📒 Files selected for processing (4)
  • .github/workflows/e2e.yml
  • e2e/about-snapshot.test.ts
  • e2e/default-invoice-template/pdf.test.ts
  • e2e/stripe-invoice-template/template.test.ts
🧰 Additional context used
🧠 Learnings (11)
📚 Learning: 2025-07-20T22:00:37.696Z
Learnt from: CR
Repo: VladSez/easy-invoice-pdf PR: 0
File: .cursor/rules/testing.mdc:0-0
Timestamp: 2025-07-20T22:00:37.696Z
Learning: Applies to **/*.test.{ts,tsx} : Use descriptive test names

Applied to files:

  • e2e/about-snapshot.test.ts
  • e2e/default-invoice-template/pdf.test.ts
  • e2e/stripe-invoice-template/template.test.ts
📚 Learning: 2025-07-20T22:00:37.696Z
Learnt from: CR
Repo: VladSez/easy-invoice-pdf PR: 0
File: .cursor/rules/testing.mdc:0-0
Timestamp: 2025-07-20T22:00:37.696Z
Learning: Use Playwright for E2E testing

Applied to files:

  • e2e/about-snapshot.test.ts
  • .github/workflows/e2e.yml
📚 Learning: 2025-07-20T22:00:37.696Z
Learnt from: CR
Repo: VladSez/easy-invoice-pdf PR: 0
File: .cursor/rules/testing.mdc:0-0
Timestamp: 2025-07-20T22:00:37.696Z
Learning: Applies to **/*.test.{ts,tsx} : Use meaningful assertions in tests

Applied to files:

  • e2e/about-snapshot.test.ts
  • e2e/default-invoice-template/pdf.test.ts
  • e2e/stripe-invoice-template/template.test.ts
📚 Learning: 2025-07-20T22:00:37.696Z
Learnt from: CR
Repo: VladSez/easy-invoice-pdf PR: 0
File: .cursor/rules/testing.mdc:0-0
Timestamp: 2025-07-20T22:00:37.696Z
Learning: Applies to **/*.test.{ts,tsx} : Integration tests must cover key user flows

Applied to files:

  • e2e/about-snapshot.test.ts
  • e2e/default-invoice-template/pdf.test.ts
  • e2e/stripe-invoice-template/template.test.ts
📚 Learning: 2025-07-20T22:00:37.696Z
Learnt from: CR
Repo: VladSez/easy-invoice-pdf PR: 0
File: .cursor/rules/testing.mdc:0-0
Timestamp: 2025-07-20T22:00:37.696Z
Learning: Applies to **/*.test.{ts,tsx} : Follow AAA pattern (Arrange, Act, Assert) in tests

Applied to files:

  • e2e/about-snapshot.test.ts
  • e2e/default-invoice-template/pdf.test.ts
  • e2e/stripe-invoice-template/template.test.ts
📚 Learning: 2025-07-20T22:00:37.696Z
Learnt from: CR
Repo: VladSez/easy-invoice-pdf PR: 0
File: .cursor/rules/testing.mdc:0-0
Timestamp: 2025-07-20T22:00:37.696Z
Learning: Applies to **/*.test.{ts,tsx} : Test error cases

Applied to files:

  • e2e/about-snapshot.test.ts
  • e2e/default-invoice-template/pdf.test.ts
  • e2e/stripe-invoice-template/template.test.ts
📚 Learning: 2025-07-20T22:00:37.696Z
Learnt from: CR
Repo: VladSez/easy-invoice-pdf PR: 0
File: .cursor/rules/testing.mdc:0-0
Timestamp: 2025-07-20T22:00:37.696Z
Learning: Applies to **/*.test.{ts,tsx} : Test cases should use it('should do something specific', () => {})

Applied to files:

  • e2e/about-snapshot.test.ts
📚 Learning: 2025-07-20T22:00:37.696Z
Learnt from: CR
Repo: VladSez/easy-invoice-pdf PR: 0
File: .cursor/rules/testing.mdc:0-0
Timestamp: 2025-07-20T22:00:37.696Z
Learning: Applies to **/*.test.{ts,tsx} : Use MSW for API mocking in tests

Applied to files:

  • e2e/default-invoice-template/pdf.test.ts
  • e2e/stripe-invoice-template/template.test.ts
📚 Learning: 2025-07-20T22:00:37.696Z
Learnt from: CR
Repo: VladSez/easy-invoice-pdf PR: 0
File: .cursor/rules/testing.mdc:0-0
Timestamp: 2025-07-20T22:00:37.696Z
Learning: Applies to **/*.test.{ts,tsx} : Keep tests isolated

Applied to files:

  • e2e/default-invoice-template/pdf.test.ts
📚 Learning: 2025-07-20T22:00:37.696Z
Learnt from: CR
Repo: VladSez/easy-invoice-pdf PR: 0
File: .cursor/rules/testing.mdc:0-0
Timestamp: 2025-07-20T22:00:37.696Z
Learning: Applies to **/*.test.{ts,tsx} : Mock external dependencies in tests

Applied to files:

  • e2e/default-invoice-template/pdf.test.ts
  • e2e/stripe-invoice-template/template.test.ts
📚 Learning: 2025-07-20T22:00:37.696Z
Learnt from: CR
Repo: VladSez/easy-invoice-pdf PR: 0
File: .cursor/rules/testing.mdc:0-0
Timestamp: 2025-07-20T22:00:37.696Z
Learning: Applies to **/*.test.{ts,tsx} : Use test data factories in tests

Applied to files:

  • e2e/stripe-invoice-template/template.test.ts
🧬 Code graph analysis (2)
e2e/default-invoice-template/pdf.test.ts (1)
e2e/utils/extended-playwright-test.ts (1)
  • test (17-33)
e2e/stripe-invoice-template/template.test.ts (3)
e2e/utils/extended-playwright-test.ts (2)
  • test (17-33)
  • expect (35-35)
e2e/stripe-invoice-template/utils.ts (2)
  • uploadBase64LogoAsFile (10-51)
  • SMALL_TEST_IMAGE_BASE64 (4-5)
src/app/schema/index.ts (2)
  • PDF_DATA_LOCAL_STORAGE_KEY (753-753)
  • InvoiceData (751-751)
🔇 Additional comments (5)
e2e/about-snapshot.test.ts (1)

1-26: Well-structured snapshot test with good determinism handling.

The test correctly pauses and resets video elements before capturing the screenshot, which prevents flakiness from video playback state. The maxDiffPixelRatio: 0.01 threshold is appropriately strict for visual regression testing.

Note: The downloadDir fixture is imported but not used in this test. Since the extended fixture also re-exports expect, this is fine, but you could alternatively import directly from @playwright/test if this file never needs download handling.

e2e/default-invoice-template/pdf.test.ts (1)

10-14: Good use of fixed system time for test determinism.

Setting the system time to a fixed date ensures invoice numbers and date-related fields are consistent across test runs. This is a best practice for avoiding flaky tests due to time-dependent data.

e2e/stripe-invoice-template/template.test.ts (2)

15-25: Good test isolation with localStorage cleanup.

The afterEach hook properly clears localStorage after each test, ensuring test isolation. Combined with the fixed system time in beforeEach, this provides a clean, deterministic environment for each test.


855-947: Good coverage for Stripe template PDF generation with logo and payment URL.

This new test properly validates the Stripe template's unique features (logo upload and payment URL) are correctly rendered in the generated PDF. The test follows the established pattern and includes proper localStorage verification before PDF generation.

.github/workflows/e2e.yml (1)

72-73: No action needed. The artifact upload path (playwright-output/test-results) and email attachment path (playwright-output/report/index.html) intentionally reference different directories. The outputDir in playwright.config.ts contains test artifacts (screenshots, videos, traces), while the HTML reporter output folder contains the visual test report. These serve different purposes and both paths are correct.

Likely an incorrect or invalid review comment.

* Updated GitHub Actions workflow to specify macOS 14 for consistency with local environment.
* Modified e2e tests to check total value after invoice generation and adjusted wait times for better stability.
* Changed URL assertions in Not Found page tests to include template parameter for accurate routing.
* Added comments to clarify updates in seller and buyer information sections in the Default Invoice Template tests.
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In @src/app/[locale]/about/page.tsx:
- Line 271: The videoSrc assignment uses a misspelled filename
"instand-download.mp4" which will 404; update the videoSrc value to use the
correct filename "instant-download.mp4" (i.e., set videoSrc to
`${IMAGEKIT_CDN_URL}/instant-download.mp4`), and scan the same file for any
other occurrences of "instand-download" to keep names consistent with the
fallback image.
🧹 Nitpick comments (2)
src/app/[locale]/about/page.tsx (2)

499-501: Consider removing animate-pulse from text content.

Continuous pulsing animation on text can be distracting and may pose accessibility concerns (WCAG 2.2.2 - Pause, Stop, Hide). For a static informational message like "no signup required," a subtle static style might be more appropriate.

♻️ Suggested fix
-            <p className="animate-pulse text-sm text-slate-400">
+            <p className="text-sm text-slate-400">

510-528: Remove commented-out code.

This block contains ~19 lines of commented-out code including an unused translation hook and old JSX markup. Dead code reduces readability; version control preserves history if needed later.

♻️ Suggested cleanup
 function Logo() {
-  // const t = useTranslations("About");
-
   return (
     <div>
-      {/* <div className="flex items-center gap-1">
-        <ProjectLogo className="size-7 flex-shrink-0 sm:size-8" />
-        <div className="flex flex-col">
-          <h1 className="text-balance text-xl font-bold text-slate-800 lg:text-2xl">
-            <a
-              href="https://dub.sh/easy-invoice?ref=about-page"
-              target="_blank"
-              rel="noopener noreferrer"
-            >
-              EasyInvoicePDF
-            </a>
-          </h1>
-        </div> */}
-      {/* <ProjectLogoDescription>{t("tagline")}</ProjectLogoDescription> */}
-      {/* </div> */}
       <div className="flex items-center">
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d6bc5d0 and 84c789b.

📒 Files selected for processing (1)
  • src/app/[locale]/about/page.tsx
🧰 Additional context used
🧠 Learnings (5)
📚 Learning: 2025-07-20T22:00:19.380Z
Learnt from: CR
Repo: VladSez/easy-invoice-pdf PR: 0
File: .cursor/rules/shared.mdc:0-0
Timestamp: 2025-07-20T22:00:19.380Z
Learning: Applies to **/*.tsx : Implement responsive design with Tailwind CSS; use a mobile-first approach.

Applied to files:

  • src/app/[locale]/about/page.tsx
📚 Learning: 2025-07-20T22:00:19.380Z
Learnt from: CR
Repo: VladSez/easy-invoice-pdf PR: 0
File: .cursor/rules/shared.mdc:0-0
Timestamp: 2025-07-20T22:00:19.380Z
Learning: Applies to **/*.tsx : Minimize 'use client', 'useEffect', and 'setState'; favor React Server Components (RSC).

Applied to files:

  • src/app/[locale]/about/page.tsx
📚 Learning: 2025-07-20T22:00:19.380Z
Learnt from: CR
Repo: VladSez/easy-invoice-pdf PR: 0
File: .cursor/rules/shared.mdc:0-0
Timestamp: 2025-07-20T22:00:19.380Z
Learning: Applies to **/*.tsx : Prefer Conditional (ternary) operator (? :) over Logical AND operator (&&) in React components.

Applied to files:

  • src/app/[locale]/about/page.tsx
📚 Learning: 2025-07-20T22:00:19.380Z
Learnt from: CR
Repo: VladSez/easy-invoice-pdf PR: 0
File: .cursor/rules/shared.mdc:0-0
Timestamp: 2025-07-20T22:00:19.380Z
Learning: Applies to **/*.tsx : Use Shadcn UI, Radix, and Tailwind for components and styling.

Applied to files:

  • src/app/[locale]/about/page.tsx
📚 Learning: 2025-07-20T22:00:19.380Z
Learnt from: CR
Repo: VladSez/easy-invoice-pdf PR: 0
File: .cursor/rules/shared.mdc:0-0
Timestamp: 2025-07-20T22:00:19.380Z
Learning: Structure files: exported component, subcomponents, helpers, static content, types.

Applied to files:

  • src/app/[locale]/about/page.tsx
🔇 Additional comments (2)
src/app/[locale]/about/page.tsx (2)

260-380: LGTM on the FeaturesSection restructure.

The video-based feature cards with alternating layout are well-implemented. The use of satisfies for type safety on FEATURES_CARDS and the cn() utility for conditional classes follows good patterns. Based on learnings, the mobile-first responsive approach is correctly applied.


168-258: LGTM on the HeroSection changes.

The rich text implementation with t.rich() for highlighted spans is clean, and the video container with Mac OS frame styling is well-structured. Responsive breakpoints follow mobile-first approach per project guidelines.

Comment thread src/app/[locale]/about/page.tsx Outdated
translationKey: "shareableLinks",
icon: <Share2 className="h-10 w-10 text-slate-700" />,
translationKey: "instantDownload",
videoSrc: `${IMAGEKIT_CDN_URL}/instand-download.mp4`,
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Typo in video filename: instand-download.mp4 should likely be instant-download.mp4.

The fallback image on line 272 uses the correct spelling instant-download-fallback.png, suggesting this is a typo that will cause a 404 error.

🔧 Suggested fix
-      videoSrc: `${IMAGEKIT_CDN_URL}/instand-download.mp4`,
+      videoSrc: `${IMAGEKIT_CDN_URL}/instant-download.mp4`,
📝 Committable suggestion

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

Suggested change
videoSrc: `${IMAGEKIT_CDN_URL}/instand-download.mp4`,
videoSrc: `${IMAGEKIT_CDN_URL}/instant-download.mp4`,
🤖 Prompt for AI Agents
In @src/app/[locale]/about/page.tsx at line 271, The videoSrc assignment uses a
misspelled filename "instand-download.mp4" which will 404; update the videoSrc
value to use the correct filename "instant-download.mp4" (i.e., set videoSrc to
`${IMAGEKIT_CDN_URL}/instant-download.mp4`), and scan the same file for any
other occurrences of "instand-download" to keep names consistent with the
fallback image.

* Changed the video demo URL to a new format for better asset management.
* Updated the fallback image used in the about page video component to align with the new demo structure.
* Removed the static assets URL import as it is no longer needed.
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In @src/app/[locale]/about/page.tsx:
- Around line 274-278: There is a typo in the feature card object: the videoSrc
string uses "instand-download.mp4" while the translationKey and fallback image
use "instant"; update the videoSrc value to use "instant-download.mp4" so it
matches videoFallbackImg and translationKey (look for the object with
translationKey: "instantDownload" and correct the videoSrc property).
🧹 Nitpick comments (2)
src/app/[locale]/about/page.tsx (2)

514-545: Remove commented-out dead code in the Logo component.

The Logo component contains a large block of commented-out JSX (lines 519-533). This clutters the codebase and provides no value since version control preserves history. Consider removing it entirely.

Proposed cleanup
 function Logo() {
-  // const t = useTranslations("About");
-
   return (
     <div>
-      {/* <div className="flex items-center gap-1">
-        <ProjectLogo className="size-7 flex-shrink-0 sm:size-8" />
-        <div className="flex flex-col">
-          <h1 className="text-balance text-xl font-bold text-slate-800 lg:text-2xl">
-            <a
-              href="https://dub.sh/easy-invoice?ref=about-page"
-              target="_blank"
-              rel="noopener noreferrer"
-            >
-              EasyInvoicePDF
-            </a>
-          </h1>
-        </div> */}
-      {/* <ProjectLogoDescription>{t("tagline")}</ProjectLogoDescription> */}
-      {/* </div> */}
       <div className="flex items-center">
         <ProjectLogo className="size-7 flex-shrink-0 sm:size-8" />
         <p className="text-balance text-center text-xl font-bold text-slate-800 sm:mt-0 sm:text-2xl lg:mr-5 lg:text-left">
           <a
             href="https://easyinvoicepdf.com"
             target="_blank"
             rel="noopener noreferrer"
           >
             EasyInvoicePDF
           </a>
         </p>
       </div>
     </div>
   );
 }

355-377: Consider extracting the repeated Mac OS frame component.

The Mac OS-style browser chrome bar with the three colored dots is duplicated in both HeroSection (lines 237-247) and FeaturesSection (lines 357-367). This pattern could be extracted into a reusable component to reduce duplication and ensure consistency.

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 84c789b and 372f809.

📒 Files selected for processing (3)
  • e2e/about.test.ts
  • src/app/[locale]/about/page.tsx
  • src/config.ts
🧰 Additional context used
🧠 Learnings (5)
📚 Learning: 2025-07-20T22:00:19.380Z
Learnt from: CR
Repo: VladSez/easy-invoice-pdf PR: 0
File: .cursor/rules/shared.mdc:0-0
Timestamp: 2025-07-20T22:00:19.380Z
Learning: Applies to **/*.tsx : Implement responsive design with Tailwind CSS; use a mobile-first approach.

Applied to files:

  • src/app/[locale]/about/page.tsx
📚 Learning: 2025-07-20T22:00:19.380Z
Learnt from: CR
Repo: VladSez/easy-invoice-pdf PR: 0
File: .cursor/rules/shared.mdc:0-0
Timestamp: 2025-07-20T22:00:19.380Z
Learning: Applies to **/*.tsx : Minimize 'use client', 'useEffect', and 'setState'; favor React Server Components (RSC).

Applied to files:

  • src/app/[locale]/about/page.tsx
📚 Learning: 2025-07-20T22:00:19.380Z
Learnt from: CR
Repo: VladSez/easy-invoice-pdf PR: 0
File: .cursor/rules/shared.mdc:0-0
Timestamp: 2025-07-20T22:00:19.380Z
Learning: Applies to **/*.tsx : Prefer Conditional (ternary) operator (? :) over Logical AND operator (&&) in React components.

Applied to files:

  • src/app/[locale]/about/page.tsx
📚 Learning: 2025-07-20T22:00:19.380Z
Learnt from: CR
Repo: VladSez/easy-invoice-pdf PR: 0
File: .cursor/rules/shared.mdc:0-0
Timestamp: 2025-07-20T22:00:19.380Z
Learning: Applies to **/*.tsx : Use Shadcn UI, Radix, and Tailwind for components and styling.

Applied to files:

  • src/app/[locale]/about/page.tsx
📚 Learning: 2025-07-20T22:00:19.380Z
Learnt from: CR
Repo: VladSez/easy-invoice-pdf PR: 0
File: .cursor/rules/shared.mdc:0-0
Timestamp: 2025-07-20T22:00:19.380Z
Learning: Structure files: exported component, subcomponents, helpers, static content, types.

Applied to files:

  • src/app/[locale]/about/page.tsx
🧬 Code graph analysis (1)
src/app/[locale]/about/page.tsx (3)
src/components/video.tsx (1)
  • Video (12-91)
src/config.ts (4)
  • VIDEO_DEMO_URL (21-21)
  • VIDEO_DEMO_FALLBACK_IMG (26-26)
  • IMAGEKIT_CDN_URL (15-16)
  • GITHUB_URL (32-32)
src/lib/utils.ts (1)
  • cn (4-6)
🔇 Additional comments (6)
src/app/[locale]/about/page.tsx (1)

10-21: LGTM on the updated imports.

The new imports (IMAGEKIT_CDN_URL, VIDEO_DEMO_FALLBACK_IMG, cn, GITHUB_URL, VIDEO_DEMO_URL) align with the refactored asset handling and config-based URLs. Good use of centralized config constants.

src/config.ts (1)

7-37: LGTM on the config constants.

Good additions with clear documentation comments explaining the purpose of each constant. The separation of STATIC_ASSETS_URL for PDF templates and IMAGEKIT_CDN_URL for landing page videos provides clear intent. The version query parameter on VIDEO_DEMO_URL is a sensible cache-busting approach.

e2e/about.test.ts (4)

1-7: LGTM on the updated imports.

The test file correctly imports VIDEO_DEMO_FALLBACK_IMG to validate the updated video poster attribute.


42-51: Good update to test assertions for hero content.

The test assertions are properly updated to validate the new hero description text and the VIDEO_DEMO_FALLBACK_IMG poster attribute, aligning with the About page changes.


69-85: Test assertions updated for features section.

The "coming soon" badge text and features description are correctly updated to match the new UI content.


183-185: Good addition of footer attribution test.

The new assertion verifying "Made by Vlad Sazonau" ensures the footer attribution is properly rendered. Consider adding similar assertions to the French and German locale tests for consistency if the attribution is localized.

Verify if the footer attribution text is the same across all locales or if it's localized. If localized, consider adding assertions in the French and German tests as well.

Comment thread src/app/[locale]/about/page.tsx Outdated
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (4)
e2e/about.test.ts (1)

26-57: Consider using STATIC_ASSETS_URL from config for font URLs.

The font URLs are hardcoded while other asset URLs (VIDEO_DEMO_URL, VIDEO_DEMO_FALLBACK_IMG) are imported from config. This inconsistency could lead to maintenance issues if the static assets domain changes.

♻️ Suggested refactor
+import {
+  GITHUB_URL,
+  MARKETING_FEATURES_CARDS,
+  STATIC_ASSETS_URL,
+  TWITTER_URL,
+  VIDEO_DEMO_FALLBACK_IMG,
+  VIDEO_DEMO_URL,
+} from "@/config";

 // Test PDF fonts
 const openSansRegularResponse = await page.request.get(
-  "https://static.easyinvoicepdf.com/open-sans-regular.ttf",
+  `${STATIC_ASSETS_URL}/open-sans-regular.ttf`,
 );

Apply similarly to the other font URLs on lines 34, 41, 47, and 53.

src/app/[locale]/about/page.tsx (3)

40-48: Unused newsletter translation variables.

These newsletter translation variables are defined but never used in the component's JSX. The translations prop passed to Footer includes them (lines 129-135), but if the newsletter UI was removed, these may be vestigial code.

Verify if the Footer component still uses these newsletter translations. If not, remove them:

#!/bin/bash
# Check if Footer component uses newsletter props
ast-grep --pattern 'newsletterTitle'
rg -n "newsletter" src/components/footer.tsx

477-511: Remove commented-out code in Logo component.

Lines 478-496 contain a large block of commented-out JSX that should be removed. Dead code clutters the codebase and creates confusion about the intended implementation.

♻️ Cleaned up Logo component
 function Logo() {
-  // const t = useTranslations("About");
-
   return (
     <div>
-      {/* <div className="flex items-center gap-1">
-        <ProjectLogo className="size-7 flex-shrink-0 sm:size-8" />
-        <div className="flex flex-col">
-          <h1 className="text-balance text-xl font-bold text-slate-800 lg:text-2xl">
-            <a
-              href="https://dub.sh/easy-invoice?ref=about-page"
-              target="_blank"
-              rel="noopener noreferrer"
-            >
-              EasyInvoicePDF
-            </a>
-          </h1>
-        </div> */}
-      {/* <ProjectLogoDescription>{t("tagline")}</ProjectLogoDescription> */}
-      {/* </div> */}
       <div className="flex items-center">
         <ProjectLogo className="size-7 flex-shrink-0 sm:size-8" />
         <p className="text-balance text-center text-xl font-bold text-slate-800 sm:mt-0 sm:text-2xl lg:mr-5 lg:text-left">
           <a
             href="https://easyinvoicepdf.com"
             target="_blank"
             rel="noopener noreferrer"
           >
             EasyInvoicePDF
           </a>
         </p>
       </div>
     </div>
   );
 }

318-340: Consider extracting Mac OS frame into a reusable component.

The Mac OS browser frame UI (lines 320-330) is duplicated from the HeroSection (lines 237-247). Extracting this into a shared component would improve maintainability.

Example extracted component:

function MacOsFrame({ children }: { children: React.ReactNode }) {
  return (
    <div className="relative overflow-hidden rounded-xl border border-slate-200 bg-white shadow-lg md:rounded-2xl md:shadow-xl">
      <div className="h-8 w-full rounded-t-xl bg-gradient-to-b from-[#F3F3F3] to-[#E9E9E9] px-4 shadow-sm md:h-12 md:rounded-t-2xl">
        <div className="flex h-full items-center">
          <div className="flex space-x-2">
            <div className="h-2.5 w-2.5 rounded-full bg-[#FF5F57] md:h-3 md:w-3" />
            <div className="h-2.5 w-2.5 rounded-full bg-[#FEBC2E] md:h-3 md:w-3" />
            <div className="h-2.5 w-2.5 rounded-full bg-[#28C840] md:h-3 md:w-3" />
          </div>
        </div>
      </div>
      {children}
    </div>
  );
}
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 372f809 and 9097315.

📒 Files selected for processing (3)
  • e2e/about.test.ts
  • src/app/[locale]/about/page.tsx
  • src/config.ts
🧰 Additional context used
🧠 Learnings (8)
📚 Learning: 2025-07-20T22:00:19.380Z
Learnt from: CR
Repo: VladSez/easy-invoice-pdf PR: 0
File: .cursor/rules/shared.mdc:0-0
Timestamp: 2025-07-20T22:00:19.380Z
Learning: Applies to **/*.tsx : Implement responsive design with Tailwind CSS; use a mobile-first approach.

Applied to files:

  • src/app/[locale]/about/page.tsx
📚 Learning: 2025-07-20T22:00:19.380Z
Learnt from: CR
Repo: VladSez/easy-invoice-pdf PR: 0
File: .cursor/rules/shared.mdc:0-0
Timestamp: 2025-07-20T22:00:19.380Z
Learning: Applies to **/*.tsx : Minimize 'use client', 'useEffect', and 'setState'; favor React Server Components (RSC).

Applied to files:

  • src/app/[locale]/about/page.tsx
📚 Learning: 2025-07-20T22:00:19.380Z
Learnt from: CR
Repo: VladSez/easy-invoice-pdf PR: 0
File: .cursor/rules/shared.mdc:0-0
Timestamp: 2025-07-20T22:00:19.380Z
Learning: Applies to **/*.tsx : Prefer Conditional (ternary) operator (? :) over Logical AND operator (&&) in React components.

Applied to files:

  • src/app/[locale]/about/page.tsx
📚 Learning: 2025-07-20T22:00:19.380Z
Learnt from: CR
Repo: VladSez/easy-invoice-pdf PR: 0
File: .cursor/rules/shared.mdc:0-0
Timestamp: 2025-07-20T22:00:19.380Z
Learning: Applies to **/*.tsx : Use Shadcn UI, Radix, and Tailwind for components and styling.

Applied to files:

  • src/app/[locale]/about/page.tsx
📚 Learning: 2025-07-20T22:00:19.380Z
Learnt from: CR
Repo: VladSez/easy-invoice-pdf PR: 0
File: .cursor/rules/shared.mdc:0-0
Timestamp: 2025-07-20T22:00:19.380Z
Learning: Structure files: exported component, subcomponents, helpers, static content, types.

Applied to files:

  • src/app/[locale]/about/page.tsx
📚 Learning: 2025-07-20T22:00:37.696Z
Learnt from: CR
Repo: VladSez/easy-invoice-pdf PR: 0
File: .cursor/rules/testing.mdc:0-0
Timestamp: 2025-07-20T22:00:37.696Z
Learning: Applies to **/*.test.{ts,tsx} : Use meaningful assertions in tests

Applied to files:

  • e2e/about.test.ts
📚 Learning: 2025-07-20T22:00:37.696Z
Learnt from: CR
Repo: VladSez/easy-invoice-pdf PR: 0
File: .cursor/rules/testing.mdc:0-0
Timestamp: 2025-07-20T22:00:37.696Z
Learning: Applies to **/*.test.{ts,tsx} : Integration tests must cover key user flows

Applied to files:

  • e2e/about.test.ts
📚 Learning: 2025-07-20T22:00:37.696Z
Learnt from: CR
Repo: VladSez/easy-invoice-pdf PR: 0
File: .cursor/rules/testing.mdc:0-0
Timestamp: 2025-07-20T22:00:37.696Z
Learning: Applies to **/*.test.{ts,tsx} : Use descriptive test names

Applied to files:

  • e2e/about.test.ts
🧬 Code graph analysis (2)
src/app/[locale]/about/page.tsx (3)
src/components/video.tsx (1)
  • Video (12-91)
src/config.ts (4)
  • VIDEO_DEMO_URL (21-21)
  • VIDEO_DEMO_FALLBACK_IMG (26-26)
  • MARKETING_FEATURES_CARDS (42-77)
  • GITHUB_URL (32-32)
src/lib/utils.ts (1)
  • cn (4-6)
e2e/about.test.ts (2)
e2e/utils/extended-playwright-test.ts (2)
  • test (17-33)
  • expect (35-35)
src/config.ts (3)
  • VIDEO_DEMO_URL (21-21)
  • VIDEO_DEMO_FALLBACK_IMG (26-26)
  • MARKETING_FEATURES_CARDS (42-77)
🔇 Additional comments (6)
src/config.ts (2)

7-26: LGTM!

The CDN configuration is well-structured with proper separation of base URL and asset paths. Using template literals for composing URLs keeps things DRY.


42-72: No action required. The filename instand-download.mp4 is correct and matches the actual CDN asset. The HTTP 200 response confirms the asset exists at that URL, while the corrected spelling returns HTTP 404, indicating the video is intentionally named with "instand" rather than "instant".

Likely an incorrect or invalid review comment.

e2e/about.test.ts (2)

58-69: LGTM!

Good coverage for feature card assets. The loop validates both video sources and fallback images for each marketing feature card. Based on learnings, the test uses meaningful assertions with specific content-type checks.


71-246: LGTM!

Comprehensive English locale test covering header, hero section, features, FAQ, and footer elements. The assertions properly verify updated content like the new hero description, video poster attributes, and footer attribution.

src/app/[locale]/about/page.tsx (2)

189-263: LGTM!

The hero section is well-implemented with proper responsive design using Tailwind's mobile-first approach. The t.rich usage for rich text interpolation with highlighted spans is a clean pattern for i18n.


350-427: LGTM!

The FAQ section uses a clean accessible pattern with native <details> elements. The FAQ_ITEMS array with type constraint matches the pattern used for MARKETING_FEATURES_CARDS.

* Moved static asset tests from the about page to a dedicated static-assets.test.ts file for better organization.
* Updated asset URLs in the config to use STATIC_ASSETS_URL for consistency.
* Registered fonts in invoice templates using the new INVOICE_PDF_FONTS structure for improved maintainability.
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In @src/config.ts:
- Line 44: The videoSrc constant currently references a misspelled filename
"instand-download.mp4"; update the string in the videoSrc assignment (the
template using STATIC_ASSETS_URL) to use "instant-download.mp4" so the path
becomes `${STATIC_ASSETS_URL}/demo-videos/instant-download.mp4`.
🧹 Nitpick comments (1)
src/config.ts (1)

15-15: Consider standardizing the cache-busting strategy.

The codebase uses inconsistent versioning approaches:

  • VIDEO_DEMO_URL: ?v=1
  • Some marketing videos: ?updatedAt=<timestamp>
  • Other assets: no version parameter

For consistency and clearer cache invalidation intent, consider adopting a uniform strategy (e.g., always use ?v=<semver> or always use timestamps).

Also applies to: 39-40, 59-60

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9097315 and 3871b2c.

📒 Files selected for processing (5)
  • e2e/about.test.ts
  • e2e/static-assets.test.ts
  • src/app/(app)/components/invoice-templates/invoice-pdf-default-template/index.tsx
  • src/app/(app)/components/invoice-templates/invoice-pdf-stripe-template/index.tsx
  • src/config.ts
🧰 Additional context used
🧠 Learnings (5)
📚 Learning: 2025-07-20T22:00:19.380Z
Learnt from: CR
Repo: VladSez/easy-invoice-pdf PR: 0
File: .cursor/rules/shared.mdc:0-0
Timestamp: 2025-07-20T22:00:19.380Z
Learning: Structure files: exported component, subcomponents, helpers, static content, types.

Applied to files:

  • src/config.ts
📚 Learning: 2025-07-20T22:00:37.696Z
Learnt from: CR
Repo: VladSez/easy-invoice-pdf PR: 0
File: .cursor/rules/testing.mdc:0-0
Timestamp: 2025-07-20T22:00:37.696Z
Learning: Applies to **/*.test.{ts,tsx} : Integration tests must cover key user flows

Applied to files:

  • e2e/static-assets.test.ts
📚 Learning: 2025-07-20T22:00:37.696Z
Learnt from: CR
Repo: VladSez/easy-invoice-pdf PR: 0
File: .cursor/rules/testing.mdc:0-0
Timestamp: 2025-07-20T22:00:37.696Z
Learning: Use Playwright for E2E testing

Applied to files:

  • e2e/static-assets.test.ts
📚 Learning: 2025-07-20T22:00:37.696Z
Learnt from: CR
Repo: VladSez/easy-invoice-pdf PR: 0
File: .cursor/rules/testing.mdc:0-0
Timestamp: 2025-07-20T22:00:37.696Z
Learning: Applies to **/*.test.{ts,tsx} : Use meaningful assertions in tests

Applied to files:

  • e2e/static-assets.test.ts
  • e2e/about.test.ts
📚 Learning: 2025-07-20T22:00:37.696Z
Learnt from: CR
Repo: VladSez/easy-invoice-pdf PR: 0
File: .cursor/rules/testing.mdc:0-0
Timestamp: 2025-07-20T22:00:37.696Z
Learning: Applies to **/*.test.{ts,tsx} : Test error cases

Applied to files:

  • e2e/static-assets.test.ts
🧬 Code graph analysis (5)
src/config.ts (2)
src/app/(app)/components/invoice-pdf-template/invoice-footer.tsx (1)
  • InvoiceFooter (9-66)
e2e/default-invoice-template/pdf.test.ts (2)
  • DATE_FORMAT (225-419)
  • page (161-223)
e2e/static-assets.test.ts (1)
src/config.ts (5)
  • VIDEO_DEMO_URL (15-15)
  • VIDEO_DEMO_FALLBACK_IMG (20-20)
  • INVOICE_PDF_FONTS (76-89)
  • MARKETING_FEATURES_CARDS (36-71)
  • STATIC_ASSETS_URL (10-10)
e2e/about.test.ts (1)
src/config.ts (1)
  • VIDEO_DEMO_FALLBACK_IMG (20-20)
src/app/(app)/components/invoice-templates/invoice-pdf-stripe-template/index.tsx (1)
src/config.ts (1)
  • INVOICE_PDF_FONTS (76-89)
src/app/(app)/components/invoice-templates/invoice-pdf-default-template/index.tsx (1)
src/config.ts (1)
  • INVOICE_PDF_FONTS (76-89)
🔇 Additional comments (9)
src/app/(app)/components/invoice-templates/invoice-pdf-stripe-template/index.tsx (2)

23-23: LGTM! Font configuration properly centralized.

The migration from inline STATIC_ASSETS_URL references to INVOICE_PDF_FONTS.STRIPE_TEMPLATE improves maintainability by centralizing asset paths in the config file.

Also applies to: 43-43, 48-48, 53-53


154-154: Verify the layout adjustment is intentional.

The colAmount flex value increased from 1.2 to 1.3. Ensure this change improves column width balance in the Stripe template and doesn't cause text overflow or layout issues with longer amounts.

src/app/(app)/components/invoice-templates/invoice-pdf-default-template/index.tsx (2)

2-2: LGTM! Font asset configuration properly centralized.

The import and font source updates align with the new INVOICE_PDF_FONTS configuration structure, improving maintainability across templates.

Also applies to: 20-20, 23-23


116-121: Verify the table column rebalancing.

Multiple column flex values have been adjusted:

  • colName: 5 → 4.8
  • colAmount: 1.1 → 1.2
  • colVAT: 0.8 → 0.9

Ensure these changes improve the table layout without causing text truncation or overflow, especially for longer item names or amounts in different locales.

e2e/static-assets.test.ts (2)

1-53: LGTM! Comprehensive static asset validation.

The test thoroughly validates:

  • Video assets (status and content-type)
  • Fallback images
  • PDF fonts across templates
  • Marketing feature card assets

The assertions are meaningful and check both HTTP status and content-type headers, which aligns with E2E testing best practices.


55-83: LGTM! Effective CDN asset monitoring approach.

The network monitoring tests use page.on("response") to track failed CDN asset loads across critical pages. This approach effectively catches asset loading issues before they reach production.

e2e/about.test.ts (2)

4-4: LGTM! Proper use of centralized config constants.

The import and usage of VIDEO_DEMO_FALLBACK_IMG aligns with the asset configuration refactoring, improving maintainability.

Also applies to: 51-51


17-17: LGTM! Test assertions updated to reflect new content.

The test assertions have been properly updated to match the new page content, including:

  • Updated page title
  • Condensed hero description
  • Revised features section text
  • Author attribution in footer

Also applies to: 44-44, 71-71, 83-83, 184-184

src/config.ts (1)

7-20: LGTM! Well-structured configuration with strong type safety.

The new configuration constants are well-organized with:

  • Clear JSDoc documentation
  • Type-safe declarations using as const satisfies
  • Logical grouping of related assets

This centralizes asset management and improves maintainability across the codebase.

Also applies to: 33-71, 73-89

Comment thread src/config.ts Outdated
},
{
translationKey: "instantDownload",
videoSrc: `${STATIC_ASSETS_URL}/demo-videos/instand-download.mp4`,
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Fix typo in video filename.

The filename contains a typo: instand-download.mp4 should be instant-download.mp4 (missing 't').

📝 Proposed fix
-    videoSrc: `${STATIC_ASSETS_URL}/demo-videos/instand-download.mp4`,
+    videoSrc: `${STATIC_ASSETS_URL}/demo-videos/instant-download.mp4`,
📝 Committable suggestion

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

Suggested change
videoSrc: `${STATIC_ASSETS_URL}/demo-videos/instand-download.mp4`,
videoSrc: `${STATIC_ASSETS_URL}/demo-videos/instant-download.mp4`,
🤖 Prompt for AI Agents
In @src/config.ts at line 44, The videoSrc constant currently references a
misspelled filename "instand-download.mp4"; update the string in the videoSrc
assignment (the template using STATIC_ASSETS_URL) to use "instant-download.mp4"
so the path becomes `${STATIC_ASSETS_URL}/demo-videos/instant-download.mp4`.

* Revised video source and fallback image URLs in the config to include versioning for better asset management.
* Ensured all demo video links follow the new naming convention for improved organization.
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

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

⚠️ Outside diff range comments (1)
e2e/stripe-invoice-template/stripe-invoice-template.test.ts (1)

306-321: Incomplete test: Missing assertion for invalid URL validation error.

The test fills an invalid URL but never asserts that an error message is displayed. This reduces the test's effectiveness for verifying validation behavior.

💚 Suggested fix
     // Try invalid URL
     await paymentUrlInput.fill("not-a-valid-url");
     await paymentUrlInput.blur();

-    // Check for validation error (this would depend on your validation implementation)
-    // The actual validation error checking would depend on how your form validation works
+    // Verify validation error is shown for invalid URL
+    await expect(
+      page.getByText(/invalid url|please enter a valid url/i),
+    ).toBeVisible();

     // Try valid URL
     await paymentUrlInput.fill("https://buy.stripe.com/test_payment_link");
     await paymentUrlInput.blur();
+
+    // Verify validation error is no longer shown
+    await expect(
+      page.getByText(/invalid url|please enter a valid url/i),
+    ).toBeHidden();

     // Should not show error for valid URL
🤖 Fix all issues with AI agents
In @src/config.ts:
- Around line 42-46: The videoSrc entry in src/config.ts for the object with
translationKey "instantDownload" has a typo in the filename
("instand-download-v1.mp4") causing the video to fail to load; update the
videoSrc value to use "instant-download-v1.mp4" so it matches the fallback image
naming and the STATIC_ASSETS_URL path.
🧹 Nitpick comments (2)
e2e/stripe-invoice-template/stripe-invoice-template.test.ts (2)

38-41: Consider using a flexible matcher for the versioned OG image URL.

The hardcoded version string ?v=1755773921680 will cause test failures when the version is updated. Consider using a regex matcher or checking only the base URL path.

♻️ Suggested approach
     await expect(page.locator('meta[property="og:image"]')).toHaveAttribute(
       "content",
-      `${STATIC_ASSETS_URL}/stripe-og.png?v=1755773921680`,
+      expect.stringMatching(new RegExp(`^${STATIC_ASSETS_URL}/stripe-og\\.png(\\?v=\\d+)?$`)),
     );

Alternatively, extract the version from a shared constant if it needs to be exact.


272-274: Consistent debounce wait pattern across tests.

Multiple tests use waitForTimeout(800) for debounce handling. While the eslint-disable comments document the intent, consider extracting this to a named constant for maintainability.

♻️ Optional: Extract debounce wait constant

Add near the top of the file:

/** Debounce timeout used throughout the app for form field updates */
const DEBOUNCE_WAIT_MS = 800;

Then replace all waitForTimeout(800) calls with:

await page.waitForTimeout(DEBOUNCE_WAIT_MS);

This makes it easier to adjust if the app's debounce timing changes.

Also applies to: 355-357, 761-763, 911-913

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3871b2c and 9530394.

📒 Files selected for processing (3)
  • e2e/invoice-form.test.ts
  • e2e/stripe-invoice-template/stripe-invoice-template.test.ts
  • src/config.ts
🧰 Additional context used
🧠 Learnings (11)
📚 Learning: 2025-07-20T22:00:37.696Z
Learnt from: CR
Repo: VladSez/easy-invoice-pdf PR: 0
File: .cursor/rules/testing.mdc:0-0
Timestamp: 2025-07-20T22:00:37.696Z
Learning: Applies to **/*.test.{ts,tsx} : Integration tests must cover key user flows

Applied to files:

  • e2e/invoice-form.test.ts
  • e2e/stripe-invoice-template/stripe-invoice-template.test.ts
📚 Learning: 2025-07-20T22:00:37.696Z
Learnt from: CR
Repo: VladSez/easy-invoice-pdf PR: 0
File: .cursor/rules/testing.mdc:0-0
Timestamp: 2025-07-20T22:00:37.696Z
Learning: Applies to **/*.test.{ts,tsx} : Use meaningful assertions in tests

Applied to files:

  • e2e/invoice-form.test.ts
  • e2e/stripe-invoice-template/stripe-invoice-template.test.ts
📚 Learning: 2025-07-20T22:00:37.696Z
Learnt from: CR
Repo: VladSez/easy-invoice-pdf PR: 0
File: .cursor/rules/testing.mdc:0-0
Timestamp: 2025-07-20T22:00:37.696Z
Learning: Applies to **/*.test.{ts,tsx} : Use descriptive test names

Applied to files:

  • e2e/invoice-form.test.ts
  • e2e/stripe-invoice-template/stripe-invoice-template.test.ts
📚 Learning: 2025-07-20T22:00:37.696Z
Learnt from: CR
Repo: VladSez/easy-invoice-pdf PR: 0
File: .cursor/rules/testing.mdc:0-0
Timestamp: 2025-07-20T22:00:37.696Z
Learning: Use Playwright for E2E testing

Applied to files:

  • e2e/invoice-form.test.ts
📚 Learning: 2025-07-20T22:00:37.696Z
Learnt from: CR
Repo: VladSez/easy-invoice-pdf PR: 0
File: .cursor/rules/testing.mdc:0-0
Timestamp: 2025-07-20T22:00:37.696Z
Learning: Applies to **/*.test.{ts,tsx} : Follow AAA pattern (Arrange, Act, Assert) in tests

Applied to files:

  • e2e/stripe-invoice-template/stripe-invoice-template.test.ts
📚 Learning: 2025-07-20T22:00:37.696Z
Learnt from: CR
Repo: VladSez/easy-invoice-pdf PR: 0
File: .cursor/rules/testing.mdc:0-0
Timestamp: 2025-07-20T22:00:37.696Z
Learning: Applies to **/*.test.{ts,tsx} : Test error cases

Applied to files:

  • e2e/stripe-invoice-template/stripe-invoice-template.test.ts
📚 Learning: 2025-07-20T22:00:37.696Z
Learnt from: CR
Repo: VladSez/easy-invoice-pdf PR: 0
File: .cursor/rules/testing.mdc:0-0
Timestamp: 2025-07-20T22:00:37.696Z
Learning: Applies to **/*.test.{ts,tsx} : Use MSW for API mocking in tests

Applied to files:

  • e2e/stripe-invoice-template/stripe-invoice-template.test.ts
📚 Learning: 2025-07-20T22:00:37.696Z
Learnt from: CR
Repo: VladSez/easy-invoice-pdf PR: 0
File: .cursor/rules/testing.mdc:0-0
Timestamp: 2025-07-20T22:00:37.696Z
Learning: Applies to **/*.test.{ts,tsx} : Use test data factories in tests

Applied to files:

  • e2e/stripe-invoice-template/stripe-invoice-template.test.ts
📚 Learning: 2025-07-20T22:00:37.696Z
Learnt from: CR
Repo: VladSez/easy-invoice-pdf PR: 0
File: .cursor/rules/testing.mdc:0-0
Timestamp: 2025-07-20T22:00:37.696Z
Learning: Applies to **/*.test.{ts,tsx} : Use Vitest for unit and integration tests

Applied to files:

  • e2e/stripe-invoice-template/stripe-invoice-template.test.ts
📚 Learning: 2025-07-20T22:00:37.696Z
Learnt from: CR
Repo: VladSez/easy-invoice-pdf PR: 0
File: .cursor/rules/testing.mdc:0-0
Timestamp: 2025-07-20T22:00:37.696Z
Learning: Applies to **/*.test.{ts,tsx} : Test cases should use it('should do something specific', () => {})

Applied to files:

  • e2e/stripe-invoice-template/stripe-invoice-template.test.ts
📚 Learning: 2025-07-20T22:00:37.696Z
Learnt from: CR
Repo: VladSez/easy-invoice-pdf PR: 0
File: .cursor/rules/testing.mdc:0-0
Timestamp: 2025-07-20T22:00:37.696Z
Learning: Applies to **/*.test.{ts,tsx} : Unit tests must achieve at least 80% coverage

Applied to files:

  • e2e/stripe-invoice-template/stripe-invoice-template.test.ts
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Run e2e tests via Vercel deployment
🔇 Additional comments (8)
e2e/stripe-invoice-template/stripe-invoice-template.test.ts (1)

16-22: Good test structure and canvas-based PDF verification.

The test suite demonstrates solid practices:

  • Fixed system time via page.clock.setSystemTime ensures deterministic date-dependent assertions.
  • Per-test downloadDir fixture properly isolates test artifacts.
  • Canvas-based PDF rendering with renderPdfOnCanvas and screenshot comparison provides reliable visual verification.
  • Tests follow the AAA pattern with clear arrange, act, and assert phases.

Based on learnings, this aligns well with integration test guidelines for key user flows.

Also applies to: 729-873, 875-975

src/config.ts (1)

76-89: Well-structured font configuration with type safety.

The as const satisfies pattern ensures both literal types and structural validation. The centralized font paths make maintenance easier.

e2e/invoice-form.test.ts (6)

23-34: Good pattern: waiting for content before URL assertion.

Using waitUntil: "commit" followed by waiting for a visible element ensures the page is fully redirected before asserting the URL. This prevents flaky tests.


50-59: Good use of config constant for OG image URL.

Using STATIC_ASSETS_URL from config makes the test maintainable and ensures consistency with the application code.


293-299: Consistent selector pattern for Tax Number fieldset.

The updated selector follows the same pattern used for Invoice Number (lines 245-255), maintaining consistency throughout the test file.


669-673: Good coverage for the new "Invoice last updated" indicator.

The assertion verifies the new UI feedback feature, ensuring users see confirmation that their changes are being tracked.


991-1012: Comprehensive VAT field validation coverage.

The tests cover boundary values (101, -1), valid numeric values (23), and custom text values (NP, OO, CUSTOM). This aligns with the updated helper text messaging.


1109-1194: Thorough test for date handling user flow.

Good use of page.clock.setSystemTime for deterministic date testing. The test covers the complete flow: detecting out-of-date values, showing helper messages, and verifying the "Update all dates" functionality. Based on learnings, this integration test effectively covers a key user flow with meaningful assertions.

Consider whether the test should also verify that updated dates persist to localStorage after the update, similar to the "persists data in local storage" test pattern earlier in the file.

Comment on lines +632 to +644
// TODO: fix below conditions
// await expect(nameFieldSwitch).toBeHidden();
// await expect(typeOfGTUFieldSwitch).toBeHidden();
// await expect(amountFieldSwitch).toBeHidden();
// await expect(unitFieldSwitch).toBeHidden();
// await expect(netPriceFieldSwitch).toBeHidden();

// // we expect vat field switch to be visible because it is the only field that is visible in stripe template
// await expect(vatFieldSwitch).toBeVisible();

// await expect(netAmountFieldSwitch).toBeHidden();
// await expect(vatAmountFieldSwitch).toBeHidden();
// await expect(preTaxAmountFieldSwitch).toBeHidden();
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Address TODO: Commented-out assertions indicate incomplete test coverage.

These commented assertions for hidden switches in Stripe template should either be implemented or removed with an explanation. Leaving TODOs with significant commented code in tests can mask regressions.

Would you like me to help investigate why these assertions are failing or open an issue to track this task?

Comment thread src/config.ts
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In @src/app/(app)/components/invoice-form/sections/invoice-items.tsx:
- Around line 513-538: The tax label input (Controller name="taxLabelText" with
id="taxLabelText" and Label htmlFor="taxLabelText") is being rendered inside the
fields.map loop causing duplicate IDs and form fields; restrict rendering to
only the first iteration by adding an isFirstItem conditional (same pattern used
for the "Show in PDF" switches) so the
Controller/Input/Label/ErrorMessage/InputHelperMessage block is rendered only
when isFirstItem is true, leaving per-item fields (e.g., items.${index}.vat)
unchanged and preserving LANGUAGE_TO_LABEL and INVOICE_PDF_TRANSLATIONS usage.
🧹 Nitpick comments (1)
src/app/(app)/components/invoice-form/sections/invoice-items.tsx (1)

167-167: Consider uncommenting the aria-labels for better accessibility.

The commented-out aria-label attributes would improve screen reader support by providing more descriptive context for these switches. While the switches have associated Label components, the aria-labels would offer additional clarity about which item and column they control.

♻️ Uncomment the aria-labels
 <Switch
   {...field}
   id={`itemNameFieldIsVisible${index}`}
   checked={value}
   onCheckedChange={onChange}
   className="h-5 w-8 [&_span]:size-4 [&_span]:data-[state=checked]:translate-x-3 rtl:[&_span]:data-[state=checked]:-translate-x-3"
-  // aria-label={`Show/hide the 'Name of Goods/Service' Column in the PDF for item ${index + 1}`}
+  aria-label={`Show/hide the 'Name of Goods/Service' Column in the PDF for item ${index + 1}`}
 />

Apply similar changes to lines 226 and 285.

Also applies to: 226-226, 285-285

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9530394 and 56c5548.

📒 Files selected for processing (1)
  • src/app/(app)/components/invoice-form/sections/invoice-items.tsx
🧰 Additional context used
🧬 Code graph analysis (1)
src/app/(app)/components/invoice-form/sections/invoice-items.tsx (4)
src/app/(app)/components/invoice-form/common.tsx (1)
  • ErrorMessage (3-5)
src/components/ui/input-helper-message.tsx (1)
  • InputHelperMessage (3-17)
src/app/schema/index.ts (1)
  • LANGUAGE_TO_LABEL (305-316)
src/app/(app)/pdf-i18n-translations/pdf-translations.ts (1)
  • INVOICE_PDF_TRANSLATIONS (211-1097)
🔇 Additional comments (5)
src/app/(app)/components/invoice-form/sections/invoice-items.tsx (5)

1-31: LGTM!

The new imports for LANGUAGE_TO_LABEL and INVOICE_PDF_TRANSLATIONS are correctly added to support the dynamic tax labeling feature.


37-59: LGTM!

The taxLabelText prop is correctly added to both the interface and function signature to support customizable tax labels.


86-105: LGTM!

The label correctly uses the dynamic taxLabelText to display the customizable tax term in the UI.


539-569: LGTM!

The per-item VAT input correctly uses the dynamic taxLabelText for the label and is properly scoped to each invoice item.


640-707: LGTM!

The dynamic taxLabelText is correctly used in labels, tooltips, and helper messages to provide consistent, customizable tax terminology throughout the invoice item fields.

Also applies to: 770-773

Comment on lines +513 to +538
<div>
<Label htmlFor="taxLabelText">Tax Label</Label>
<Controller
name="taxLabelText"
control={control}
render={({ field }) => (
<Input
{...field}
type="text"
id="taxLabelText"
placeholder="Enter tax label (e.g., VAT, Tax, GST, Sales Tax)"
className="mt-1 block w-full"
/>
)}
/>
{errors.taxLabelText && (
<ErrorMessage>{errors.taxLabelText.message}</ErrorMessage>
)}
/>
{!errors.taxLabelText && (
<InputHelperMessage>
Customize the tax label on your invoice (e.g., VAT, Sales
Tax, IVA). {LANGUAGE_TO_LABEL[language]} default:{" "}
{INVOICE_PDF_TRANSLATIONS[language].invoiceItemsTable.vat}
</InputHelperMessage>
)}
</div>
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Critical: Tax Label input is rendered multiple times with duplicate IDs.

The Tax Label input is inside the fields.map loop (line 108) but lacks an isFirstItem conditional check. This causes the input to be rendered once for every invoice item, creating:

  • Duplicate IDs: id="taxLabelText" appears multiple times (HTML validation error)
  • Duplicate form fields: Only the last input's value will be submitted
  • Accessibility violation: <Label htmlFor="taxLabelText"> references multiple inputs
  • Poor UX: Users see the same global setting repeated for each item

Since taxLabelText is a global field (not per-item like items.${index}.vat), it should only be rendered once.

🐛 Proposed fix: Add isFirstItem check
-              <div>
+              {isFirstItem ? (
+                <div>
                   <Label htmlFor="taxLabelText">Tax Label</Label>
                   <Controller
                     name="taxLabelText"
                     control={control}
                     render={({ field }) => (
                       <Input
                         {...field}
                         type="text"
                         id="taxLabelText"
                         placeholder="Enter tax label (e.g., VAT, Tax, GST, Sales Tax)"
                         className="mt-1 block w-full"
                       />
                     )}
                   />
                   {errors.taxLabelText && (
                     <ErrorMessage>{errors.taxLabelText.message}</ErrorMessage>
                   )}
                   {!errors.taxLabelText && (
                     <InputHelperMessage>
                       Customize the tax label on your invoice (e.g., VAT, Sales
                       Tax, IVA). {LANGUAGE_TO_LABEL[language]} default:{" "}
                       {INVOICE_PDF_TRANSLATIONS[language].invoiceItemsTable.vat}
                     </InputHelperMessage>
                   )}
                 </div>
+              ) : null}

This follows the same pattern used for other global settings like the "Show in PDF" switches (e.g., lines 486-510).

📝 Committable suggestion

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

Suggested change
<div>
<Label htmlFor="taxLabelText">Tax Label</Label>
<Controller
name="taxLabelText"
control={control}
render={({ field }) => (
<Input
{...field}
type="text"
id="taxLabelText"
placeholder="Enter tax label (e.g., VAT, Tax, GST, Sales Tax)"
className="mt-1 block w-full"
/>
)}
/>
{errors.taxLabelText && (
<ErrorMessage>{errors.taxLabelText.message}</ErrorMessage>
)}
/>
{!errors.taxLabelText && (
<InputHelperMessage>
Customize the tax label on your invoice (e.g., VAT, Sales
Tax, IVA). {LANGUAGE_TO_LABEL[language]} default:{" "}
{INVOICE_PDF_TRANSLATIONS[language].invoiceItemsTable.vat}
</InputHelperMessage>
)}
</div>
{isFirstItem ? (
<div>
<Label htmlFor="taxLabelText">Tax Label</Label>
<Controller
name="taxLabelText"
control={control}
render={({ field }) => (
<Input
{...field}
type="text"
id="taxLabelText"
placeholder="Enter tax label (e.g., VAT, Tax, GST, Sales Tax)"
className="mt-1 block w-full"
/>
)}
/>
{errors.taxLabelText && (
<ErrorMessage>{errors.taxLabelText.message}</ErrorMessage>
)}
{!errors.taxLabelText && (
<InputHelperMessage>
Customize the tax label on your invoice (e.g., VAT, Sales
Tax, IVA). {LANGUAGE_TO_LABEL[language]} default:{" "}
{INVOICE_PDF_TRANSLATIONS[language].invoiceItemsTable.vat}
</InputHelperMessage>
)}
</div>
) : null}
🤖 Prompt for AI Agents
In @src/app/(app)/components/invoice-form/sections/invoice-items.tsx around
lines 513 - 538, The tax label input (Controller name="taxLabelText" with
id="taxLabelText" and Label htmlFor="taxLabelText") is being rendered inside the
fields.map loop causing duplicate IDs and form fields; restrict rendering to
only the first iteration by adding an isFirstItem conditional (same pattern used
for the "Show in PDF" switches) so the
Controller/Input/Label/ErrorMessage/InputHelperMessage block is rendered only
when isFirstItem is true, leaving per-item fields (e.g., items.${index}.vat)
unchanged and preserving LANGUAGE_TO_LABEL and INVOICE_PDF_TRANSLATIONS usage.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

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

⚠️ Outside diff range comments (2)
src/app/(app)/page.client.tsx (2)

260-338: URL compare path should apply handleInvoiceNumberBreakingChange too.

When urlData is present, you parse decompressed URL invoice data without the invoice-number breaking-change patch. That can make “old” shared links look “invalid” and trigger the “incorrect URL” toast during edits.

Proposed diff
             const decompressedInvoiceDataFromUrl = decompressInvoiceData(
               urlParsed as Record<string, unknown>,
             );

-            const validatediInvoiceDataFromUrl = invoiceSchema.parse(
-              decompressedInvoiceDataFromUrl,
-            );
+            const updatedUrlJson = handleInvoiceNumberBreakingChange(
+              decompressedInvoiceDataFromUrl,
+            );
+            const validatedInvoiceDataFromUrl = invoiceSchema.parse(updatedUrlJson);

             const invoiceHasChanged =
-              JSON.stringify(validatediInvoiceDataFromUrl) !==
+              JSON.stringify(validatedInvoiceDataFromUrl) !==
               JSON.stringify(newInvoiceDataValidated);

699-705: Add rel="noopener noreferrer" on external link with target="_blank".

This is a reverse-tabnabbing footgun.

Proposed diff
         <a
           href="https://dub.sh/easy-invoice-pdf-feedback"
           className="transition-colors hover:text-blue-600 hover:underline"
           target="_blank"
+          rel="noopener noreferrer"
         >
           Share your feedback
         </a>
🤖 Fix all issues with AI agents
In @src/app/(app)/page.client.tsx:
- Around line 550-564: The prop typing for handleShareInvoice is wrong:
InvoicePageHeader currently types it as () => void but the actual implementation
is async and returns Promise<void>; update the prop type in the
InvoicePageHeader parameter/type annotation to () => Promise<void> (and update
any related prop interfaces or usages that declare handleShareInvoice) so the
component signature matches the async function.
🧹 Nitpick comments (7)
src/app/(app)/page.client.tsx (4)

25-57: Unify get-app-metadata imports to a single module path.

You’re importing getAppMetadata via an alias path and DEFAULT_METADATA via a relative path, which can create duplicate module instances / bundling oddities.

Proposed diff
-import { getAppMetadata } from "@/app/(app)/utils/get-app-metadata";
+import {
+  DEFAULT_METADATA,
+  getAppMetadata,
+} from "@/app/(app)/utils/get-app-metadata";
 ...
-import { DEFAULT_METADATA } from "./utils/get-app-metadata";

112-170: Don’t let metadata write failures wipe out invoice loading.

loadFromLocalStorage writes metadata inside the same try as invoice parsing; if localStorage.setItem fails (quota/private mode), you fall into the catch and reset invoice data.

Proposed diff (isolate metadata init)
   const loadFromLocalStorage = useCallback(() => {
     try {
-      const appMetadata = getAppMetadata();
-
-      // add metadata with default values if missing for all users
-      if (!appMetadata) {
-        localStorage.setItem(
-          METADATA_LOCAL_STORAGE_KEY,
-          JSON.stringify(DEFAULT_METADATA),
-        );
-      }
+      try {
+        const appMetadata = getAppMetadata();
+        if (!appMetadata) {
+          localStorage.setItem(
+            METADATA_LOCAL_STORAGE_KEY,
+            JSON.stringify(DEFAULT_METADATA),
+          );
+        }
+      } catch (e) {
+        Sentry.captureException(e);
+      }

       const savedData = localStorage.getItem(PDF_DATA_LOCAL_STORAGE_KEY);

177-233: Handle decompressFromEncodedURIComponent possibly returning null.

If decompression returns null, JSON.parse(decompressedData) will throw a less actionable error. Consider an explicit check and a clearer error path (and similarly isolate metadata writes so URL-loaded invoices don’t get discarded on metadata failures).


352-440: Guard localStorage.setItem inside the CTA setTimeout (can throw).

Even if you fix the main localStorage availability check, localStorage.setItem(CTA_TOAST_LAST_SHOWN_STORAGE_KEY, ...) runs later in a timer without a try/catch; quota/private-mode failures can become unhandled errors.

e2e/generate-invoice-link.test.ts (3)

252-261: Consider extracting the custom tax label to a constant.

The assertion on line 260 uses the hardcoded string "Custom VAT" which duplicates the value set on line 134. Extracting this to a named constant (e.g., TEST_TAX_LABEL) would improve maintainability and make the relationship between setup and assertion more explicit.

♻️ Proposed refactor

At the top of the file, add:

+const TEST_TAX_LABEL = "Custom VAT";
+
 const TEST_SELLER_DATA = {

Then use it in both locations:

 await taxSettingsFieldset
   .getByRole("textbox", { name: "Tax Label" })
-  .fill("Custom VAT");
+  .fill(TEST_TAX_LABEL);

And in the assertion:

 await expect(
-  newTaxSettingsFieldset.getByRole("textbox", { name: "Custom VAT" }),
+  newTaxSettingsFieldset.getByRole("textbox", { name: TEST_TAX_LABEL }),
 ).toHaveValue("23");

318-319: Consider extracting the old uncompressed URL to a top-level constant.

The 800+ character URL string embedded in the test makes it harder to read the test logic. Moving it to the top of the file alongside TEST_SELLER_DATA and TEST_BUYER_DATA would improve readability.

♻️ Proposed refactor

Move to the top of the file after the test data constants:

 } as const satisfies BuyerData;
 
+const OLD_UNCOMPRESSED_URL =
+  "N4IgNghgdg5grhGBTEAuEAHMIA0IAmEALkgGID2ATgLbFogCyTDABACI4sCaPXuIAYziVKSKAICe9AKoBlNvxLUsxFOgDORSgEsMKPGHIxy9ftqgA3ctoFIAcnGoAjJJQDyTgFZIBRNKEgXbHRSCABrImEIFihKdDwLCDA4NRAARgB6AAYADgBaACYsgoBWEABfPEISNwAzAEl1dRT6ItK83Ly0sqrVOtlXCxtUtpKO-IBmNLNLa1sAFQk9egAlJAtXdSQWAGEACwhKZBmrYcW9Um0kMHxGgDVtdW0nMDUtFLwtsFfKfxBtfD0NIAdgALFkAGw5UElCagiZZHogKAQaipABS5D2UHY5H0IAg+Hwoia9AAMuQoPhKZxpABpfiJIh2EzoNIFOElCGM4gsy7XW7qB5PF5vSgfEBIWjaYIgTxYqAAAWlYAAdAJyNR+BABBq4FBmY4XL82RyYdy8Dq9QaHM5XPybvdHs9Xmh3khPgB3bS1IgAIRsQLNXP46m9voDAgdguFLrFEqg5BI6noyb8eETyejTpFrtQtSSW0qICccAkrj+AKBwNhoIhWRhJWBDf4KLR9AAggI0bsTJaiSSU+g7EhPdwqGFOHYuLTZB2eczWelgxaQEy+VdHULnaK3eKPZKVfQdWjlRAZerNa2k0ghyBr1nNzGd3n3cXtEohwBtUDmU62eolFtY0czjPcE1RVJZHIX1PUObY2HWa5yAwNEDVbSDs23XN4wPIgliQOoAHF5mkUw8HwvRiNIrDY13fNCwPVFyH1PxUDSS1qBYg1aJfXC8H1D96C2SghlsfhBKIXicPAg8oCQIgAAUdHE9iSiyDSMwU5ThmksDUHdBI6GHRSFz0+jDORBSOy41i0G6DSsi0ogbO4qSn1Aiz9yMlzbPQ1AnLXYhXNY8zX28zBRHmCAAA8Qv8hzNMipBorivz3IFTzwpScoAF0KKTJJ7PUpKmWi0VZEcWhKAkLL+MwCAJDQogGAUvZyEBdBvVEFgtGgdRagrPAMEa5rWqIdr8DC+qRqasQiDYFp0FGcZClBUMtF0JBFMatwoDAcwkGkShZQfW9ViQygthYAQDiOfFM1vabZOGzZKQ7OAJqobQAC8kHweZyDWWxtA2Z6DIivQrvez72p0P6AfIRpmjIDzsP0t8gA";
+
 test.describe("Generate Invoice Link", () => {

Then use it in the test:

-  const OLD_UNCOMPRESSED_URL =
-    "N4IgNghgdg5grhGBTEAuEAHMIA0IAmEALkgGID2ATgLbFogCyTDABACI4sCaPXuIAYziVKSKAICe9AKoBlNvxLUsxFOgDORSgEsMKPGHIxy9ftqgA3ctoFIAcnGoAjJJQDyTgFZIBRNKEgXbHRSCABrImEIFihKdDwLCDA4NRAARgB6AAYADgBaACYsgoBWEABfPEISNwAzAEl1dRT6ItK83Ly0sqrVOtlXCxtUtpKO-IBmNLNLa1sAFQk9egAlJAtXdSQWAGEACwhKZBmrYcW9Um0kMHxGgDVtdW0nMDUtFLwtsFfKfxBtfD0NIAdgALFkAGw5UElCagiZZHogKAQaipABS5D2UHY5H0IAg+Hwoia9AAMuQoPhKZxpABpfiJIh2EzoNIFOElCGM4gsy7XW7qB5PF5vSgfEBIWjaYIgTxYqAAAWlYAAdAJyNR+BABBq4FBmY4XL82RyYdy8Dq9QaHM5XPybvdHs9Xmh3khPgB3bS1IgAIRsQLNXP46m9voDAgdguFLrFEqg5BI6noyb8eETyejTpFrtQtSSW0qICccAkrj+AKBwNhoIhWRhJWBDf4KLR9AAggI0bsTJaiSSU+g7EhPdwqGFOHYuLTZB2eczWelgxaQEy+VdHULnaK3eKPZKVfQdWjlRAZerNa2k0ghyBr1nNzGd3n3cXtEohwBtUDmU62eolFtY0czjPcE1RVJZHIX1PUObY2HWa5yAwNEDVbSDs23XN4wPIgliQOoAHF5mkUw8HwvRiNIrDY13fNCwPVFyH1PxUDSS1qBYg1aJfXC8H1D96C2SghlsfhBKIXicPAg8oCQIgAAUdHE9iSiyDSMwU5ThmksDUHdBI6GHRSFz0+jDORBSOy41i0G6DSsi0ogbO4qSn1Aiz9yMlzbPQ1AnLXYhXNY8zX28zBRHmCAAA8Qv8hzNMipBorivz3IFTzwpScoAF0KKTJJ7PUpKmWi0VZEcWhKAkLL+MwCAJDQogGAUvZyEBdBvVEFgtGgdRagrPAMEa5rWqIdr8DC+qRqasQiDYFp0FGcZClBUMtF0JBFMatwoDAcwkGkShZQfW9ViQygthYAQDiOfFM1vabZOGzZKQ7OAJqobQAC8kHweZyDWWxtA2Z6DIivQrvez72p0P6AfIRpmjIDzsP0t8gA";
-
   //  Navigate to old uncompressed URL

439-447: Consider extracting the compression ratio threshold to a named constant.

The hardcoded 20 on line 447 represents a minimum expected compression ratio. Extracting this to a named constant would make the test's intent clearer and easier to adjust if compression algorithms change.

♻️ Proposed refactor

At the top of the file:

+const MIN_COMPRESSION_RATIO_PERCENT = 20;
+
 const TEST_SELLER_DATA = {

Then use it in the assertion:

-// Verify significant compression occurred (at least 20% reduction)
-expect(compressionRatio).toBeGreaterThan(20);
+// Verify significant compression occurred
+expect(compressionRatio).toBeGreaterThan(MIN_COMPRESSION_RATIO_PERCENT);
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 56c5548 and acf2ed5.

📒 Files selected for processing (2)
  • e2e/generate-invoice-link.test.ts
  • src/app/(app)/page.client.tsx
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2025-07-20T22:00:37.696Z
Learnt from: CR
Repo: VladSez/easy-invoice-pdf PR: 0
File: .cursor/rules/testing.mdc:0-0
Timestamp: 2025-07-20T22:00:37.696Z
Learning: Applies to **/*.test.{ts,tsx} : Use test data factories in tests

Applied to files:

  • e2e/generate-invoice-link.test.ts
📚 Learning: 2025-07-20T22:00:37.696Z
Learnt from: CR
Repo: VladSez/easy-invoice-pdf PR: 0
File: .cursor/rules/testing.mdc:0-0
Timestamp: 2025-07-20T22:00:37.696Z
Learning: Applies to **/*.test.{ts,tsx} : Use meaningful assertions in tests

Applied to files:

  • e2e/generate-invoice-link.test.ts
🧬 Code graph analysis (2)
e2e/generate-invoice-link.test.ts (1)
src/app/schema/index.ts (2)
  • SellerData (533-533)
  • BuyerData (574-574)
src/app/(app)/page.client.tsx (4)
src/app/(app)/utils/get-app-metadata.tsx (2)
  • getAppMetadata (30-58)
  • DEFAULT_METADATA (13-18)
src/app/schema/index.ts (3)
  • METADATA_LOCAL_STORAGE_KEY (807-807)
  • PDF_DATA_LOCAL_STORAGE_KEY (753-753)
  • InvoiceData (751-751)
src/utils/url-compression.ts (1)
  • decompressInvoiceData (239-243)
src/app/(app)/utils/invoice-number-breaking-change.ts (1)
  • handleInvoiceNumberBreakingChange (18-66)
🔇 Additional comments (5)
src/app/(app)/page.client.tsx (2)

477-492: Footer copy update looks good.

“Made by” is consistent and reads better.


235-243: No action needed. isLocalStorageAvailable is a const holding a boolean value (the result of storageAvailable("localStorage")), not a function. The guard if (!isLocalStorageAvailable) correctly checks whether localStorage is unavailable and properly returns early.

Likely an incorrect or invalid review comment.

e2e/generate-invoice-link.test.ts (3)

4-36: LGTM! Excellent use of test data factories.

The test data constants follow best practices with type-safe as const satisfies pattern and provide reusable, well-structured test data. This aligns well with the project's testing guidelines.


127-143: LGTM! Comprehensive tax settings coverage.

The test properly verifies the new customizable tax label functionality and validates the total calculation (5 × 100 × 1.23 = 615.00).


147-147: The 700ms timeout appropriately exceeds the application's DEBOUNCE_TIMEOUT of 500ms (defined in src/app/(app)/components/invoice-form/index.tsx). While hardcoded timeouts in e2e tests can be fragile, the 200ms buffer provides reasonable cushion for slower CI environments. This is acceptable as-is, though consider adding a comment explaining the relationship to the app's 500ms debounce if the timeout needs adjustment in the future.

Comment on lines +550 to +564
function InvoicePageHeader({
canShareInvoice,
handleShareInvoice,
isDesktop,
invoiceDataState,
errorWhileGeneratingPdfIsShown,
setErrorWhileGeneratingPdfIsShown,
}: {
canShareInvoice: boolean;
handleShareInvoice: () => void;
isDesktop: boolean;
invoiceDataState: InvoiceData;
errorWhileGeneratingPdfIsShown: boolean;
setErrorWhileGeneratingPdfIsShown: (value: boolean) => void;
}) {
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Fix prop typing: handleShareInvoice is async but typed as () => void.

handleShareInvoice returns Promise<void> but InvoicePageHeader declares it as () => void, which should be a TS type error.

Proposed diff
 function InvoicePageHeader({
   canShareInvoice,
   handleShareInvoice,
   isDesktop,
   invoiceDataState,
   errorWhileGeneratingPdfIsShown,
   setErrorWhileGeneratingPdfIsShown,
 }: {
   canShareInvoice: boolean;
-  handleShareInvoice: () => void;
+  handleShareInvoice: () => Promise<void>;
   isDesktop: boolean;
   invoiceDataState: InvoiceData;
   errorWhileGeneratingPdfIsShown: boolean;
   setErrorWhileGeneratingPdfIsShown: (value: boolean) => void;
 }) {
📝 Committable suggestion

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

Suggested change
function InvoicePageHeader({
canShareInvoice,
handleShareInvoice,
isDesktop,
invoiceDataState,
errorWhileGeneratingPdfIsShown,
setErrorWhileGeneratingPdfIsShown,
}: {
canShareInvoice: boolean;
handleShareInvoice: () => void;
isDesktop: boolean;
invoiceDataState: InvoiceData;
errorWhileGeneratingPdfIsShown: boolean;
setErrorWhileGeneratingPdfIsShown: (value: boolean) => void;
}) {
function InvoicePageHeader({
canShareInvoice,
handleShareInvoice,
isDesktop,
invoiceDataState,
errorWhileGeneratingPdfIsShown,
setErrorWhileGeneratingPdfIsShown,
}: {
canShareInvoice: boolean;
handleShareInvoice: () => Promise<void>;
isDesktop: boolean;
invoiceDataState: InvoiceData;
errorWhileGeneratingPdfIsShown: boolean;
setErrorWhileGeneratingPdfIsShown: (value: boolean) => void;
}) {
🤖 Prompt for AI Agents
In @src/app/(app)/page.client.tsx around lines 550 - 564, The prop typing for
handleShareInvoice is wrong: InvoicePageHeader currently types it as () => void
but the actual implementation is async and returns Promise<void>; update the
prop type in the InvoicePageHeader parameter/type annotation to () =>
Promise<void> (and update any related prop interfaces or usages that declare
handleShareInvoice) so the component signature matches the async function.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Show on ui when invoice was last saved in local storage Allow custom tax format name

1 participant