Rebrand platform to Alias and launch AEOS promotional system#1
Rebrand platform to Alias and launch AEOS promotional system#1danmarauda wants to merge 12 commits intomainfrom
Conversation
Create AGENTS.md, .cursorrules,.windsurfrules, and Copilot instructions.
Use Next.js Script component to prevent flash of unstyled content.
Add complex promotional page with custom CSS, 3D WebGL, sections, animations
Include ALIAS Brand link in GettingStarted section navigation.
Create new Twenty CRM integration files, update validation schemas, and rebrand existing tools to Alias.
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
WalkthroughRebrands the project to "Alias", adds a Twenty CRM integration (client, server actions, registry, env/validation), introduces AI tooling and new AI API routes, adds Alias showcase pages (chat, generator, alias promo with Three.js), and introduces developer guardrails/Copilot guidance and many marketing/layout updates. Changes
Sequence Diagram(s)sequenceDiagram
actor User
participant Form as SubscribeForm (Client)
participant Action as twentySubscriptionAction (Server Action)
participant RateLimit as RateLimiter
participant Turnstile as Turnstile (Captcha)
participant TwentyAPI as Twenty CRM (GraphQL)
User->>Form: Submit email form
Form->>Action: POST FormData
Action->>RateLimit: Check IP namespaced limit
alt rate limited
RateLimit-->>Action: throttled
Action-->>Form: return 429 / rate_limit_exceeded_
else allowed
RateLimit-->>Action: allowed
Action->>Turnstile: validate token
alt invalid
Turnstile-->>Action: invalid
Action-->>Form: return fieldErrors.turnstile
else valid
Turnstile-->>Action: valid
Action->>Action: validate fields (email format, required)
alt validation fails
Action-->>Form: return 400 with fieldErrors
else valid
Action->>TwentyAPI: find/create person / add subscriber
TwentyAPI-->>Action: success/failure
alt failure
Action-->>Form: return 500 / subscription_failed_
else success
Action-->>Form: return 200 / subscription_successful_
end
end
end
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
🤖 Augment PR SummarySummary: This PR rebrands the platform to Alias, adds a new ALIAS/AEOS promotional brand system page, and migrates newsletter signup flows from HubSpot/Mailchimp toward Twenty CRM. Changes:
Technical Notes: Twenty CRM env vars ( 🤖 Was this summary useful? React with 👍 or 👎 |
| } | ||
| }) | ||
|
|
||
| const geometry = useMemo(() => new THREE.BoxGeometry(0.95, 0.95, 0.95), []) |
There was a problem hiding this comment.
app/(examples)/alias/_components/cube-scene/index.tsx:39: This introduces useMemo, which violates the project guardrail “No Manual Memoization” (React Compiler handles it) (Rule: CLAUDE.md). This is likely to fail CI/lint in this repo.
Other locations where this applies: app/(examples)/alias/_components/cube-scene/index.tsx:40, app/(examples)/alias/_components/cube-scene/index.tsx:58, app/(examples)/alias/_components/cube-scene/index.tsx:70
Severity: high
🤖 Was this useful? React with 👍 or 👎, or 🚀 if it prevented an incident/outage.
| } | ||
| }) | ||
|
|
||
| const geometry = useMemo(() => new THREE.BoxGeometry(0.95, 0.95, 0.95), []) |
There was a problem hiding this comment.
app/(examples)/alias/_components/cube-scene/index.tsx:39: BoxGeometry/EdgesGeometry are allocated but there’s no explicit disposal on unmount, which conflicts with the “WebGL Cleanup Mandatory” rule (Rule: CLAUDE.md). This can leak GPU memory across navigations/re-mounts.
Severity: medium
🤖 Was this useful? React with 👍 or 👎, or 🚀 if it prevented an incident/outage.
| // Calculate phase for fragmentation | ||
| let newPhase = (Math.sin(time * 0.6) + 1) / 2 | ||
| newPhase = Math.pow(newPhase, 4) | ||
| setPhase(newPhase) |
There was a problem hiding this comment.
app/(examples)/alias/_components/cube-scene/index.tsx:88: Calling setPhase(newPhase) inside useFrame forces a React render every animation frame, which can cause jank/high CPU in the WebGL scene under load.
Severity: medium
🤖 Was this useful? React with 👍 or 👎, or 🚀 if it prevented an incident/outage.
| <span className={cn(s.tData, 'text-white')}>ALIAS—PROMO-PACK / V1.0</span> | ||
| </div> | ||
| <div className={s.navLinks}> | ||
| <a href="#motifs" className={s.navLink}> |
There was a problem hiding this comment.
app/(examples)/alias/page.tsx:792: Raw <a> elements are disallowed here by the no-anchor-element guardrail (Rule: CLAUDE.md). This is likely to fail CI/lint; use the project Link wrapper instead.
Other locations where this applies: app/(examples)/alias/page.tsx:795, app/(examples)/alias/page.tsx:798, app/(examples)/alias/page.tsx:801, app/(examples)/alias/page.tsx:804
Severity: high
🤖 Was this useful? React with 👍 or 👎, or 🚀 if it prevented an incident/outage.
| import type { FormState } from '@/components/ui/form/types' | ||
| import { rateLimit, rateLimiters } from '@/lib/utils/rate-limit' | ||
| import { emailSchema } from '@/utils/validation' | ||
| import { validateFormWithTurnstile } from '../mailchimp/turnstile' |
There was a problem hiding this comment.
lib/integrations/twenty/action.ts:7: This ../mailchimp/turnstile parent-relative import violates the path-alias/no-../ rule (Rule: CLAUDE.md). It also makes the new Twenty integration depend on the deprecated Mailchimp integration, which undermines integration modularity/removability.
Severity: high
🤖 Was this useful? React with 👍 or 👎, or 🚀 if it prevented an incident/outage.
| } | ||
|
|
||
| // Create a note with the form submission details | ||
| const noteResult = await createNote({ |
There was a problem hiding this comment.
lib/integrations/twenty/client.ts:372: addContactFromForm claims it “attaches a note with the form message”, but the createNote call doesn’t reference/link the created/found person, so the note may end up orphaned rather than associated with that contact.
Severity: medium
🤖 Was this useful? React with 👍 or 👎, or 🚀 if it prevented an incident/outage.
|
|
||
| // Create new person | ||
| const result = await createPerson({ | ||
| name: { |
There was a problem hiding this comment.
lib/integrations/twenty/client.ts:409: Newsletter signup can create a person with empty firstName/lastName (since these are optional inputs), which may violate Twenty API expectations or produce low-quality CRM records.
Severity: medium
🤖 Was this useful? React with 👍 or 👎, or 🚀 if it prevented an incident/outage.
| }) | ||
|
|
||
| /** Environment variables required by the Twenty CRM integration. */ | ||
| export const twentyEnvSchema = z.object({ |
There was a problem hiding this comment.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 9e9e204403
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| const turnstileValidation = await validateFormWithTurnstile(formData) | ||
| if (!turnstileValidation.isValid) { | ||
| for (const error of turnstileValidation.errors) { |
There was a problem hiding this comment.
Require Turnstile token only when the form provides it
This action always validates Turnstile, but the new /subscribe form only submits an email field and never includes cf-turnstile-response. In production environments where NEXT_PUBLIC_CLOUDFLARE_TURNSTILE_SITE_KEY is configured, validateFormWithTurnstile will reject every submission, so all newsletter signups fail with invalid_input_.
Useful? React with 👍 / 👎.
| const noteResult = await createNote({ | ||
| title: `Contact Form: ${contactData.subject}`, | ||
| bodyV2: { | ||
| markdown: `**From:** ${contactData.name} (${contactData.email})\n\n**Subject:** ${contactData.subject}\n\n**Message:**\n\n${contactData.message}`, | ||
| }, |
There was a problem hiding this comment.
Attach contact notes to the created/found person
addContactFromForm fetches or creates a person, but the subsequent createNote call does not use that person at all. As written, notes are created as unlinked records, so CRM workflows that rely on person-note association won't see the submitted message on the contact timeline.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Actionable comments posted: 22
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In @.github/copilot-instructions.md:
- Around line 9-57: Add blank lines before and after each Markdown heading and
surrounding code blocks to satisfy formatting consistency; specifically insert
an empty line after headings "Always Use", "Never Use", "Component Pattern",
"Server vs Client", "Styling", "Tech Stack", and "Docs", and ensure code blocks
(the TSX examples around the "Always Use" and "Component Pattern" sections) have
a blank line both above and below the triple-backtick fences so the snippets are
separated from adjacent headings and text.
In `@app/`(examples)/alias/_components/cube-scene/index.tsx:
- Around line 39-40: The code currently uses useMemo for stable
construction-time values (cubePositions, basePositions, geometry, edgesGeometry)
which violates the "no manual memoization" guideline; move cubePositions and
basePositions to module-scope constants and remove their useMemo wrappers, and
replace geometry and edgesGeometry useMemo calls by creating those Three.js
objects either at module scope (if safe) or by initializing them once with refs
inside the component (e.g., create them in a ref in the component body and
reuse), or better yet use declarative R3F primitives where available; update
references in the component to use the module-scope constants or the
ref-initialized geometry/edgesGeometry and delete the corresponding useMemo
imports/usages (also apply same changes for the similar blocks around the 58-73
region).
- Around line 75-93: Replace the per-frame React state update in the useFrame
callback: stop calling setPhase every frame and instead store the computed phase
in a ref (e.g., phaseRef.current) inside the useFrame callback used by
CubeGroup; update the child Cube components to read phase from that ref rather
than from React state so they don't rerender each frame. Add a small state/ref
to track the current label and only call onStateChange when the label actually
flips between 'FRAGMENTED' and 'ORCHESTRATED'. Also remove the unnecessary
useMemo calls used for geometry/materials (the ones wrapping values currently
memoized) so the React Compiler can handle optimization automatically.
In `@app/`(examples)/alias/page.module.css:
- Around line 55-59: Media queries in this module use a 768px breakpoint but the
project standard desktop breakpoint is 800px; update each `@media` (min-width:
768px) to `@media` (min-width: 800px) so the .navLinks, and the other media blocks
mentioned (the ones around the selectors at the later occurrences) match the
repo-wide desktop breakpoint; ensure you update all occurrences (including the
blocks affecting .navLinks and the two other ranges flagged) to maintain
consistent responsive behavior.
- Around line 235-317: The animations currently animate background-size and
border-color (keyframes lattice-pulse and target-lock-1..4 used by
.animateLattice and .targetP1/.targetP2/.targetP3/.targetP4), which causes extra
painting and ignores motion preferences; update those keyframes to animate only
transform and opacity (e.g., replace background-size changes in lattice-pulse
with a subtle scale/translate and remove border-color changes from all
target-lock-* keyframes), and add a prefers-reduced-motion media query that
disables the animations (set animation: none !important for .animateLattice and
.targetP1/.targetP2/.targetP3/.targetP4) so motion-sensitive users and GPU-heavy
pages avoid unnecessary paint work.
In `@app/`(examples)/alias/page.tsx:
- Around line 120-126: The hover transition on the BracketBox micro-grid uses
non-compositor properties (see the class string containing 'transition-all
duration-700 group-hover:opacity-100 opacity-50' applied via cn with s.microGrid
and s.animateLattice), which triggers paint; change it to only animate
compositor-safe properties by replacing 'transition-all' with
'transition-opacity' or a transform-based transition (e.g., 'transition-opacity
duration-700' or 'transition-transform duration-700') or move the hover visual
into an absolutely positioned overlay that fades (only animating
opacity/transform). Apply the same change for the other occurrences referenced
(around the classes at lines ~510-512 and ~551) so all hover transitions follow
the transform/opacity-only guideline.
- Around line 79-824: The file is failing Biome's useSortedClasses rule
(unsorted Tailwind classes across components from MotifsSection through the
footer), so normalize all Tailwind class attribute values in this file (e.g., in
MotifsSection, CardVariantA/B/C, DigitalSection, ComponentsSection,
RationaleSection, and AliasPage) to the canonical sorted order expected by your
tooling; either run your auto-fixer (Biome/Prettier Tailwind plugin) to reorder
class lists or manually reorder className strings to match useSortedClasses,
then re-run "bun run check" to verify the lint errors are resolved before
committing.
- Around line 791-807: Replace the raw anchor tags inside the navLinks block
with the app's custom Link component used for shared link behavior: change each
<a href="#motifs" className={s.navLink}>... (and the similar entries for `#cards`,
`#digital`, `#components`, `#rationale`) to use <Link href="#..."
className={s.navLink}>..., ensuring Link is imported in the module and that the
className and fragment href values are preserved; apply the same replacement for
any remaining section links in page.tsx so no raw <a> elements remain.
- Around line 1-6: The page currently has "use client" at the top causing the
whole page to be a client component; extract the interactive cube into a
dedicated client child by moving "use client" into a new client component that
renders CubeSceneWrapper and replacing the MotifsSection's internal cube state
with that new component (look for MotifsSection and CubeSceneWrapper to locate
the code); replace raw <a> nav links with the Link component imported from
"@/components/ui/link" where the nav anchors are rendered (search for the nav
anchor elements) and update their props accordingly; find any usages of the
"transition-colors" utility within this page/sections and remove or replace them
with compositor-only transitions (transform/opacity) so animations avoid paint
work (search for "transition-colors" class names and adjust to use
transform/opacity-only classes).
In `@app/`(examples)/hubspot/page.tsx:
- Around line 13-15: The HubspotPage currently calls redirect('/subscribe'),
which issues a temporary 307; replace that call with
permanentRedirect('/subscribe') so the route returns an HTTP 308 permanent
redirect indicating the page is permanently retired—update the HubspotPage
function to use permanentRedirect instead of redirect.
In `@app/`(examples)/subscribe/_components/subscribe-form/index.tsx:
- Around line 6-16: Update the SubscribeForm public props to extend HTML div
props by changing SubscribeFormProps to extend import type ComponentProps<'div'>
(i.e., import type { ComponentProps } from 'react') so callers can pass id,
data-*, ARIA, etc.; adjust the SubscribeForm signature to collect ...props (or
restProps) and spread those onto the root wrapper <div> in the SubscribeForm
component, preserving existing className, placeholder, and submitText handling.
In `@components/layout/theme/index.tsx`:
- Around line 82-88: The theme-setting Script currently rendered in the client
component Theme (Script id="theme-script" using strategy="beforeInteractive")
won't run before hydration here; move the inline theme setter into the root
layout (app/layout.tsx) so it executes pre-hydration, read the initial theme
from a persisted source (cookie or localStorage) there and set
document.documentElement.dataset.theme before React mounts, keep the existing
useEffect in Theme for runtime/dynamic changes, and add a runtime guard where
you previously interpolated currentTheme (check currentTheme exists in the
themes keys/object) before injecting it to avoid unsafe values.
In `@components/ui/not-configured/index.tsx`:
- Around line 51-58: Update the "Mailchimp" integration entry in the
NotConfigured UI so it no longer points users to Mailchimp setup: locate the
"Mailchimp" object key in components/ui/not-configured/index.tsx (the
integration map used by the NotConfigured component) and either remove that
"Mailchimp" entry entirely or replace its values (description, docsUrl, envVars)
with the correct Twenty CRM migration guidance and env var names; ensure the
CTA/description matches the new target and that any rendered envVars reflect
Twenty CRM keys rather than MAILCHIMP_*.
In `@lib/integrations/registry.ts`:
- Around line 64-68: The registry contains deprecated integrations (e.g.,
HubSpot, Mailchimp) but has no deprecation metadata, so getConfigured() and
getUnconfigured() still surface them; add an optional boolean flag (e.g.,
deprecated: true) to the IntegrationEntry type/interface and set it on retired
entries (or remove the entries outright), then update the discovery functions
getConfigured() and getUnconfigured() to filter out any registry entries where
entry.deprecated === true before returning or iterating so deprecated
integrations are excluded from setup/doctor discovery.
In `@lib/integrations/twenty/action.ts`:
- Line 7: The Twenty integration currently imports validateFormWithTurnstile
from the Mailchimp module, coupling Twenty to a deprecated integration; extract
the Turnstile helper into a shared module (e.g., lib/shared/turnstile or
lib/integrations/turnstile) and move the validateFormWithTurnstile
implementation there, then update the import in
lib/integrations/twenty/action.ts to point to the new shared module (replace
validateFormWithTurnstile import) and remove any Mailchimp-specific dependencies
from the moved code so Twenty no longer depends on lib/integrations/mailchimp.
- Around line 35-80: Centralize validation by replacing the manual checks on
rawData and the separate emailSchema check with a single Zod schema that expects
trimmed strings (create a normalizedPayload by trimming formData values first or
use z.preprocess to trim) and validate both required and email format in that
schema; run validateFormWithTurnstile as before, then z.parse/z.safeParse the
normalized data (e.g., the payload used where rawData is created), convert Zod
errors into the fieldErrors map (keys: name, email, subject, message) and return
the same 400 shape on failure; update the same approach for the other block
referenced (lines 124-153) so all server actions use the shared Zod schema.
- Around line 21-26: The current IP key uses a literal 'unknown' which causes
all requests without x-forwarded-for to share one throttle bucket; update the
logic around headers() and the rateLimit call so you first try x-forwarded-for,
then fall back to other trusted headers (e.g., x-real-ip or cf-connecting-ip)
and only if you still have no stable client identifier avoid using a shared
'unknown' key — either skip IP-based throttling or use a non-shared global key
(e.g., `twenty-contact:global`) and apply whichever change consistently for both
occurrences that call rateLimit with `twenty-contact:${ip}`; adjust the ip
variable and the rateLimit invocation accordingly and keep rateLimiters.standard
unchanged.
In `@lib/integrations/twenty/client.ts`:
- Around line 355-377: The note is being created unattached because createNote
is only given title/body; update the createNote call (after
findPersonByEmail/createPerson) to include the resolved person relationship
using the person variable (e.g., pass person.id or the API's
persons/people/personId field or association array as required by createNote's
input shape) so the note is attached to that contact record; also ensure you
handle any error from noteResult as you do for person creation.
- Around line 157-168: Replace raw process.env usage in getTwentyConfig by
importing the typed env from "@/lib/env" and reading env.TWENTY_API_URL and
env.TWENTY_API_KEY so Zod validation/typing is used; update the function to
return { apiUrl: env.TWENTY_API_URL, apiKey: env.TWENTY_API_KEY } (and remove
the manual process.env presence check), ensuring the returned shape still
matches TwentyConfig; keep an explicit throw only if you must guard against
runtime absence, but prefer relying on the env schema for validation.
- Around line 395-416: The addSubscriber function currently only creates or
finds a Person but never records newsletter consent; update it so that after
finding an existing person (via findPersonByEmail) you persist an opt-in marker
(e.g., addTag("newsletter_opt_in") or set a custom field like
newsletterOptIn=true via updatePerson), and when creating a new person (via
createPerson) include that same consent field/tag/note in the payload (or call
the same add-tag/update API immediately after creation); reference
addSubscriber, findPersonByEmail, createPerson and ensure behavior matches how
addContactFromForm differentiates newsletter subscribers (use the same
tag/field/note convention), returning { success: true } only after the consent
marker is successfully written and surface errors if the consent write fails.
- Around line 379-384: twentyContactAction currently returns { success: true }
even when createNote() fails and the user's message is lost; change the behavior
to propagate the failure or return an explicit partial result instead of a clean
success. Locate the call to createNote and the return statement in
twentyContactAction (and any surrounding handlers that interpret its result) and
either throw or return { success: false, error: noteResult.error } when
noteResult.success is false, or return an explicit partial response like {
success: false, partial: true, personCreated: true, noteError: noteResult.error
} and add logic to queue a retry for the note creation. Ensure callers that
expect message_sent_ handle the new error/partial shape.
In `@lib/utils/validation.ts`:
- Around line 82-89: Update twentyEnvSchema so TWENTY_API_URL is validated as a
proper URL (use z.string().url() or z.url()) instead of only non-empty; this
ensures isConfigured() (which relies on twentyEnvSchema.safeParse(...).success)
only returns true for valid URLs and prevents enabling the integration with
values like "foo". Keep TWENTY_API_KEY as the existing non-empty string check
and mirror the validation style used by coreEnvSchema for consistency.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
Run ID: 0c20b72d-1f04-451a-a914-f7bd36775232
📒 Files selected for processing (26)
.cursorrules.github/copilot-instructions.md.windsurfrulesCLAUDE.mdCOMPONENTS.mdapp/(examples)/alias/_components/cube-scene/index.tsxapp/(examples)/alias/page.module.cssapp/(examples)/alias/page.tsxapp/(examples)/hubspot/page.tsxapp/(examples)/subscribe/_components/subscribe-form/index.tsxapp/(examples)/subscribe/page.tsxapp/(marketing)/_sections/features/index.tsxapp/(marketing)/_sections/getting-started/index.tsxapp/(marketing)/_sections/presets/index.tsxcomponents/layout/theme/index.tsxcomponents/ui/not-configured/index.tsxlib/env.tslib/integrations/README.mdlib/integrations/check-integration.tslib/integrations/registry.tslib/integrations/twenty/README.mdlib/integrations/twenty/action.tslib/integrations/twenty/client.tslib/styles/css/tailwind.csslib/styles/fonts.tslib/utils/validation.ts
| ### Always Use | ||
| ```tsx | ||
| import { Image } from '@/components/ui/image' // NOT next/image | ||
| import { Link } from '@/components/ui/link' // NOT next/link | ||
| import s from './component.module.css' // CSS Modules as 's' | ||
| import type { ComponentProps } from 'react' // type-only imports | ||
| ``` | ||
|
|
||
| ### Never Use | ||
| - `useMemo`, `useCallback`, `React.memo` (React Compiler handles it) | ||
| - `any` type (use `unknown` + narrowing) | ||
| - Relative imports with `../` (use `@/` aliases) | ||
| - Raw `<a>` or `<img>` tags | ||
| - `h-screen` (use `h-dvh`) | ||
|
|
||
| ### Component Pattern | ||
| ```tsx | ||
| import s from './component.module.css' | ||
| import cn from 'clsx' | ||
| import type { ComponentProps } from 'react' | ||
|
|
||
| interface Props extends ComponentProps<'div'> { | ||
| variant?: 'primary' | 'secondary' | ||
| } | ||
|
|
||
| export function Component({ variant = 'primary', className, ...props }: Props) { | ||
| return <div className={cn(s.root, className)} {...props} /> | ||
| } | ||
| ``` | ||
|
|
||
| ### Server vs Client | ||
| - Default to Server Components | ||
| - Add `'use client'` only for: hooks, event handlers, browser APIs | ||
| - Keep data fetching in Server Components | ||
|
|
||
| ### Styling | ||
| - Tailwind for spacing/colors/typography | ||
| - CSS Modules for animations/complex layouts | ||
| - Combine with `cn()` from clsx | ||
|
|
||
| ## Tech Stack | ||
| - Bun (package manager) | ||
| - Biome (linting, not ESLint) | ||
| - Tailwind v4 + CSS Modules | ||
| - GSAP, Lenis (animation) | ||
| - Zustand (state) | ||
| - Zod (validation) | ||
|
|
||
| ## Docs |
There was a problem hiding this comment.
🧹 Nitpick | 🔵 Trivial
Consider adding blank lines around headings and code blocks for markdown consistency.
Static analysis flagged multiple markdown formatting issues: headings at lines 9, 17, 24, 39, 44, 49, 57 lack blank lines below them, and code blocks at lines 10, 25 lack surrounding blank lines. While this doesn't affect functionality, consistent formatting improves readability.
Example fix for lines 9-15
### Always Use
+
```tsx
import { Image } from '@/components/ui/image' // NOT next/image
import { Link } from '@/components/ui/link' // NOT next/link
import s from './component.module.css' // CSS Modules as 's'
import type { ComponentProps } from 'react' // type-only importsNever Use
</details>
<details>
<summary>🧰 Tools</summary>
<details>
<summary>🪛 markdownlint-cli2 (0.22.0)</summary>
[warning] 9-9: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
---
[warning] 10-10: Fenced code blocks should be surrounded by blank lines
(MD031, blanks-around-fences)
---
[warning] 17-17: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
---
[warning] 24-24: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
---
[warning] 25-25: Fenced code blocks should be surrounded by blank lines
(MD031, blanks-around-fences)
---
[warning] 39-39: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
---
[warning] 44-44: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
---
[warning] 49-49: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
---
[warning] 57-57: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
</details>
</details>
<details>
<summary>🤖 Prompt for AI Agents</summary>
Verify each finding against the current code and only fix it if needed.
In @.github/copilot-instructions.md around lines 9 - 57, Add blank lines before
and after each Markdown heading and surrounding code blocks to satisfy
formatting consistency; specifically insert an empty line after headings "Always
Use", "Never Use", "Component Pattern", "Server vs Client", "Styling", "Tech
Stack", and "Docs", and ensure code blocks (the TSX examples around the "Always
Use" and "Component Pattern" sections) have a blank line both above and below
the triple-backtick fences so the snippets are separated from adjacent headings
and text.
</details>
<!-- fingerprinting:phantom:poseidon:ocelot:82a7cd2c-ac9c-4efc-bc73-8164b2cc2dd7 -->
<!-- This is an auto-generated comment by CodeRabbit -->
| const geometry = useMemo(() => new THREE.BoxGeometry(0.95, 0.95, 0.95), []) | ||
| const edgesGeometry = useMemo(() => new THREE.EdgesGeometry(geometry), [geometry]) |
There was a problem hiding this comment.
🧹 Nitpick | 🔵 Trivial
Lift the static scene data out of useMemo.
cubePositions, basePositions, and the geometries are all stable construction-time values. Keeping them behind useMemo adds manual memoization hooks the repo explicitly avoids; move the positions to module scope and initialize the Three.js objects via refs or declarative R3F primitives instead.
As per coding guidelines: Never use manual memoization - avoid useMemo, useCallback, and React.memo as React Compiler handles optimization.
Also applies to: 58-73
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@app/`(examples)/alias/_components/cube-scene/index.tsx around lines 39 - 40,
The code currently uses useMemo for stable construction-time values
(cubePositions, basePositions, geometry, edgesGeometry) which violates the "no
manual memoization" guideline; move cubePositions and basePositions to
module-scope constants and remove their useMemo wrappers, and replace geometry
and edgesGeometry useMemo calls by creating those Three.js objects either at
module scope (if safe) or by initializing them once with refs inside the
component (e.g., create them in a ref in the component body and reuse), or
better yet use declarative R3F primitives where available; update references in
the component to use the module-scope constants or the ref-initialized
geometry/edgesGeometry and delete the corresponding useMemo imports/usages (also
apply same changes for the similar blocks around the 58-73 region).
| useFrame(({ clock }) => { | ||
| if (!groupRef.current) return | ||
|
|
||
| const time = clock.getElapsedTime() | ||
|
|
||
| // Rotate the group | ||
| groupRef.current.rotation.y = time * 0.3 | ||
| groupRef.current.rotation.x = Math.sin(time * 0.2) * 0.2 | ||
| groupRef.current.position.y = Math.sin(time * 0.5) * 0.1 | ||
|
|
||
| // Calculate phase for fragmentation | ||
| let newPhase = (Math.sin(time * 0.6) + 1) / 2 | ||
| newPhase = Math.pow(newPhase, 4) | ||
| setPhase(newPhase) | ||
|
|
||
| // Update state text | ||
| if (onStateChange) { | ||
| onStateChange(newPhase > 0.05 ? 'FRAGMENTED' : 'ORCHESTRATED') | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
cat -n app/\(examples\)/alias/_components/cube-scene/index.tsxRepository: danmarauda/satus
Length of output: 5451
Don't call React state setters from useFrame — track phase by reference instead.
Line 88 updates React state every frame, triggering rerenders of CubeGroup and all 8 child Cube components. Since the phase value is computed fresh on every frame and the child components' animation logic is already running imperatively in their own useFrame hooks, store phase in a useRef instead and read it from the ref. This eliminates the per-frame React rerender overhead. Similarly, only invoke onStateChange when the state label actually transitions between 'FRAGMENTED' and 'ORCHESTRATED', not every frame.
Additionally, remove the useMemo calls on lines 39–40 and 58–73. Per coding guidelines, the React Compiler handles optimization automatically; manual memoization is unnecessary and should be avoided.
🧰 Tools
🪛 Biome (2.4.10)
[error] 87-87: Use the '**' operator instead of 'Math.pow'.
(lint/style/useExponentiationOperator)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@app/`(examples)/alias/_components/cube-scene/index.tsx around lines 75 - 93,
Replace the per-frame React state update in the useFrame callback: stop calling
setPhase every frame and instead store the computed phase in a ref (e.g.,
phaseRef.current) inside the useFrame callback used by CubeGroup; update the
child Cube components to read phase from that ref rather than from React state
so they don't rerender each frame. Add a small state/ref to track the current
label and only call onStateChange when the label actually flips between
'FRAGMENTED' and 'ORCHESTRATED'. Also remove the unnecessary useMemo calls used
for geometry/materials (the ones wrapping values currently memoized) so the
React Compiler can handle optimization automatically.
| @media (min-width: 768px) { | ||
| .navLinks { | ||
| display: flex; | ||
| } | ||
| } |
There was a problem hiding this comment.
Use the repo desktop breakpoint here.
These media queries switch at 768px, so this page will diverge from the rest of the responsive system around tablet widths. Update them to the project-standard 800px breakpoint.
As per coding guidelines, "Desktop breakpoint is 800px".
Also applies to: 83-87, 340-345
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@app/`(examples)/alias/page.module.css around lines 55 - 59, Media queries in
this module use a 768px breakpoint but the project standard desktop breakpoint
is 800px; update each `@media` (min-width: 768px) to `@media` (min-width: 800px) so
the .navLinks, and the other media blocks mentioned (the ones around the
selectors at the later occurrences) match the repo-wide desktop breakpoint;
ensure you update all occurrences (including the blocks affecting .navLinks and
the two other ranges flagged) to maintain consistent responsive behavior.
| @keyframes lattice-pulse { | ||
| 0% { | ||
| background-size: 20px 20px; | ||
| opacity: 0.3; | ||
| } | ||
| 50% { | ||
| background-size: 24px 24px; | ||
| opacity: 0.8; | ||
| border-color: var(--alias-border-hover); | ||
| } | ||
| 100% { | ||
| background-size: 20px 20px; | ||
| opacity: 0.3; | ||
| } | ||
| } | ||
|
|
||
| .animateLattice { | ||
| animation: lattice-pulse 6s infinite ease-in-out; | ||
| } | ||
|
|
||
| @keyframes target-lock-1 { | ||
| 0%, 100% { | ||
| transform: translate(-10px, -10px); | ||
| opacity: 0.3; | ||
| } | ||
| 50% { | ||
| transform: translate(0, 0); | ||
| opacity: 1; | ||
| border-color: var(--alias-fg); | ||
| } | ||
| } | ||
|
|
||
| @keyframes target-lock-2 { | ||
| 0%, 100% { | ||
| transform: translate(10px, -10px); | ||
| opacity: 0.3; | ||
| } | ||
| 50% { | ||
| transform: translate(0, 0); | ||
| opacity: 1; | ||
| border-color: var(--alias-fg); | ||
| } | ||
| } | ||
|
|
||
| @keyframes target-lock-3 { | ||
| 0%, 100% { | ||
| transform: translate(-10px, 10px); | ||
| opacity: 0.3; | ||
| } | ||
| 50% { | ||
| transform: translate(0, 0); | ||
| opacity: 1; | ||
| border-color: var(--alias-fg); | ||
| } | ||
| } | ||
|
|
||
| @keyframes target-lock-4 { | ||
| 0%, 100% { | ||
| transform: translate(10px, 10px); | ||
| opacity: 0.3; | ||
| } | ||
| 50% { | ||
| transform: translate(0, 0); | ||
| opacity: 1; | ||
| border-color: var(--alias-fg); | ||
| } | ||
| } | ||
|
|
||
| .targetP1 { | ||
| animation: target-lock-1 4s infinite cubic-bezier(0.16, 1, 0.3, 1); | ||
| } | ||
|
|
||
| .targetP2 { | ||
| animation: target-lock-2 4s infinite cubic-bezier(0.16, 1, 0.3, 1); | ||
| } | ||
|
|
||
| .targetP3 { | ||
| animation: target-lock-3 4s infinite cubic-bezier(0.16, 1, 0.3, 1); | ||
| } | ||
|
|
||
| .targetP4 { | ||
| animation: target-lock-4 4s infinite cubic-bezier(0.16, 1, 0.3, 1); | ||
| } |
There was a problem hiding this comment.
These looping effects need a performance/accessibility pass.
lattice-pulse animates background-size and border-color, the target-lock-* keyframes animate border-color, and none of them are disabled under prefers-reduced-motion. On a WebGL-heavy page, that adds avoidable paint work and ignores motion-sensitive users. Keep the motion on transform/opacity and provide a reduced-motion fallback.
As per coding guidelines, "Animate only transform and opacity properties; avoid animating other CSS properties".
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@app/`(examples)/alias/page.module.css around lines 235 - 317, The animations
currently animate background-size and border-color (keyframes lattice-pulse and
target-lock-1..4 used by .animateLattice and
.targetP1/.targetP2/.targetP3/.targetP4), which causes extra painting and
ignores motion preferences; update those keyframes to animate only transform and
opacity (e.g., replace background-size changes in lattice-pulse with a subtle
scale/translate and remove border-color changes from all target-lock-*
keyframes), and add a prefers-reduced-motion media query that disables the
animations (set animation: none !important for .animateLattice and
.targetP1/.targetP2/.targetP3/.targetP4) so motion-sensitive users and GPU-heavy
pages avoid unnecessary paint work.
| function getTwentyConfig(): TwentyConfig { | ||
| const apiUrl = process.env.TWENTY_API_URL | ||
| const apiKey = process.env.TWENTY_API_KEY | ||
|
|
||
| if (!(apiUrl && apiKey)) { | ||
| throw new Error( | ||
| 'Missing Twenty CRM configuration. Please set TWENTY_API_URL and TWENTY_API_KEY environment variables.' | ||
| ) | ||
| } | ||
|
|
||
| return { apiUrl, apiKey } | ||
| } |
There was a problem hiding this comment.
🛠️ Refactor suggestion | 🟠 Major
Use the typed env surface instead of raw process.env.
This duplicates the Twenty configuration contract and can drift from the Zod schema already registered for integrations. Read these values from env so validation and typing stay centralized.
As per coding guidelines, "Typed environment access via env from @/lib/env with Zod schema validation".
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@lib/integrations/twenty/client.ts` around lines 157 - 168, Replace raw
process.env usage in getTwentyConfig by importing the typed env from "@/lib/env"
and reading env.TWENTY_API_URL and env.TWENTY_API_KEY so Zod validation/typing
is used; update the function to return { apiUrl: env.TWENTY_API_URL, apiKey:
env.TWENTY_API_KEY } (and remove the manual process.env presence check),
ensuring the returned shape still matches TwentyConfig; keep an explicit throw
only if you must guard against runtime absence, but prefer relying on the env
schema for validation.
| let person = await findPersonByEmail(contactData.email) | ||
|
|
||
| // Create person if not exists | ||
| if (!person) { | ||
| const personResult = await createPerson({ | ||
| name: { firstName, lastName }, | ||
| email: contactData.email, | ||
| }) | ||
|
|
||
| if (!personResult.success || !personResult.person) { | ||
| return { success: false, error: personResult.error } | ||
| } | ||
|
|
||
| person = personResult.person | ||
| } | ||
|
|
||
| // Create a note with the form submission details | ||
| const noteResult = await createNote({ | ||
| title: `Contact Form: ${contactData.subject}`, | ||
| bodyV2: { | ||
| markdown: `**From:** ${contactData.name} (${contactData.email})\n\n**Subject:** ${contactData.subject}\n\n**Message:**\n\n${contactData.message}`, | ||
| }, | ||
| }) |
There was a problem hiding this comment.
The note is never associated with the resolved contact.
person is found/created above, but createNote() only receives the title/body payload, so this writes a standalone note rather than attaching the submission to that CRM record. The contact form won't deliver on its own "attach note" contract until the note mutation includes the person relationship.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@lib/integrations/twenty/client.ts` around lines 355 - 377, The note is being
created unattached because createNote is only given title/body; update the
createNote call (after findPersonByEmail/createPerson) to include the resolved
person relationship using the person variable (e.g., pass person.id or the API's
persons/people/personId field or association array as required by createNote's
input shape) so the note is attached to that contact record; also ensure you
handle any error from noteResult as you do for person creation.
| if (!noteResult.success) { | ||
| // Person was created but note failed - still consider partial success | ||
| console.warn('Twenty: Person created but note creation failed:', noteResult.error) | ||
| } | ||
|
|
||
| return { success: true } |
There was a problem hiding this comment.
Don't return success after losing the user's message.
If createNote() fails, the function still resolves { success: true }, so twentyContactAction() reports message_sent_ even though the message body never reached the CRM. At minimum, propagate the failure; if partial success is intentional, surface it explicitly and queue a retry instead of treating it as a clean success.
🐛 Minimal fix
if (!noteResult.success) {
- // Person was created but note failed - still consider partial success
- console.warn('Twenty: Person created but note creation failed:', noteResult.error)
+ return {
+ success: false,
+ error: noteResult.error ?? 'Failed to persist contact message',
+ }
}
return { success: true }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@lib/integrations/twenty/client.ts` around lines 379 - 384,
twentyContactAction currently returns { success: true } even when createNote()
fails and the user's message is lost; change the behavior to propagate the
failure or return an explicit partial result instead of a clean success. Locate
the call to createNote and the return statement in twentyContactAction (and any
surrounding handlers that interpret its result) and either throw or return {
success: false, error: noteResult.error } when noteResult.success is false, or
return an explicit partial response like { success: false, partial: true,
personCreated: true, noteError: noteResult.error } and add logic to queue a
retry for the note creation. Ensure callers that expect message_sent_ handle the
new error/partial shape.
| export async function addSubscriber( | ||
| subscriptionData: SubscriptionData | ||
| ): Promise<{ success: boolean; error?: string }> { | ||
| try { | ||
| // Check if person already exists | ||
| const existingPerson = await findPersonByEmail(subscriptionData.email) | ||
|
|
||
| if (existingPerson) { | ||
| // Already subscribed | ||
| return { success: true } | ||
| } | ||
|
|
||
| // Create new person | ||
| const result = await createPerson({ | ||
| name: { | ||
| firstName: subscriptionData.firstName || '', | ||
| lastName: subscriptionData.lastName || '', | ||
| }, | ||
| email: subscriptionData.email, | ||
| }) | ||
|
|
||
| return result |
There was a problem hiding this comment.
Persist subscriber consent, not just a person record.
This helper either creates a generic person or returns early for an existing contact, but it never writes any field/tag/note that marks the record as newsletter-opted-in. That leaves newsletter signups indistinguishable from the generic people created by addContactFromForm(), and re-subscribing an existing contact becomes a no-op.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@lib/integrations/twenty/client.ts` around lines 395 - 416, The addSubscriber
function currently only creates or finds a Person but never records newsletter
consent; update it so that after finding an existing person (via
findPersonByEmail) you persist an opt-in marker (e.g.,
addTag("newsletter_opt_in") or set a custom field like newsletterOptIn=true via
updatePerson), and when creating a new person (via createPerson) include that
same consent field/tag/note in the payload (or call the same add-tag/update API
immediately after creation); reference addSubscriber, findPersonByEmail,
createPerson and ensure behavior matches how addContactFromForm differentiates
newsletter subscribers (use the same tag/field/note convention), returning {
success: true } only after the consent marker is successfully written and
surface errors if the consent write fails.
| export const twentyEnvSchema = z.object({ | ||
| TWENTY_API_URL: z | ||
| .string() | ||
| .min(1, { error: 'TWENTY_API_URL is required' }), | ||
| TWENTY_API_KEY: z | ||
| .string() | ||
| .min(1, { error: 'TWENTY_API_KEY is required' }), | ||
| }) |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
# First, let's examine the validation.ts file to see the current state around lines 82-89
cat -n lib/utils/validation.ts | sed -n '75,95p'Repository: danmarauda/satus
Length of output: 924
🏁 Script executed:
# Check if coreEnvSchema uses z.url()
rg -A 10 "coreEnvSchema" lib/utils/validation.tsRepository: danmarauda/satus
Length of output: 387
🏁 Script executed:
# Check the isConfigured() function in registry.ts
rg -B 3 -A 10 "isConfigured" lib/integrations/registry.tsRepository: danmarauda/satus
Length of output: 477
🏁 Script executed:
# Search for TWENTY_API_URL usage throughout the codebase
rg "TWENTY_API_URL" --type ts --type tsx -B 2 -A 2Repository: danmarauda/satus
Length of output: 87
🏁 Script executed:
# Try searching for TWENTY_API_URL with broader approach
rg "TWENTY_API_URL" -B 2 -A 2Repository: danmarauda/satus
Length of output: 2891
🏁 Script executed:
# Check the integrations registry to see how twentyEnvSchema is used
rg -B 5 -A 5 "twentyEnvSchema" lib/integrations/registry.tsRepository: danmarauda/satus
Length of output: 604
TWENTY_API_URL needs URL format validation.
Currently, any non-empty string marks Twenty as configured because isConfigured() only checks safeParse(...).success. A value like TWENTY_API_URL=foo passes validation and enables the integration, then fails later during API calls. Align this with coreEnvSchema and validate as a URL using z.url().
🔧 Proposed fix
export const twentyEnvSchema = z.object({
- TWENTY_API_URL: z
- .string()
- .min(1, { error: 'TWENTY_API_URL is required' }),
+ TWENTY_API_URL: z.url({
+ error: 'TWENTY_API_URL must be a valid URL',
+ }),
TWENTY_API_KEY: z
.string()
.min(1, { error: 'TWENTY_API_KEY is required' }),
})🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@lib/utils/validation.ts` around lines 82 - 89, Update twentyEnvSchema so
TWENTY_API_URL is validated as a proper URL (use z.string().url() or z.url())
instead of only non-empty; this ensures isConfigured() (which relies on
twentyEnvSchema.safeParse(...).success) only returns true for valid URLs and
prevents enabling the integration with values like "foo". Keep TWENTY_API_KEY as
the existing non-empty string check and mirror the validation style used by
coreEnvSchema for consistency.
Complete rebranding and new CRM integration with thorough documentation.
There was a problem hiding this comment.
Actionable comments posted: 4
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (3)
ARCHITECTURE.md (1)
138-138: 🧹 Nitpick | 🔵 TrivialFooter attribution inconsistent with Alias rebrand.
The documentation footer still references "Satūs" and the darkroom.engineering repo. Consider updating to align with the Alias branding used elsewhere.
♻️ Suggested update
-*Built with [Satūs](https://github.com/darkroomengineering/satus) by [darkroom.engineering](https://darkroom.engineering)* +*Powered by [Alias](https://github.com/danmarauda/satus)*🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@ARCHITECTURE.md` at line 138, Update the footer attribution in ARCHITECTURE.md where the string "*Built with [Satūs](https://github.com/darkroomengineering/satus) by [darkroom.engineering](https://darkroom.engineering)*" appears: replace the "Satūs" text and link and the "darkroom.engineering" text and link with the Alias branding and appropriate Alias repository/website URL so the footer matches the rest of the project; ensure the new text and links use the same markdown formatting and phrasing as the original footer.app/layout.tsx (1)
98-99: 🧹 Nitpick | 🔵 TrivialInconsistent branding:
satusVersionstill references old name.The metadata has been rebranded to "Alias", but the version tracking script still uses
satusVersion. Consider aligning this with the rebrand for consistency.♻️ Suggested update
- {/* this helps to track Satus usage thanks to Wappalyzer */} - <Script async>{`window.satusVersion = '${AppData.version}';`}</Script> + {/* this helps to track Alias usage thanks to Wappalyzer */} + <Script async>{`window.aliasVersion = '${AppData.version}';`}</Script>🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@app/layout.tsx` around lines 98 - 99, The version tracking script still sets window.satusVersion using AppData.version; update the key to the new brand (e.g., window.aliasVersion) to match the rebrand. Modify the Script component that contains `window.satusVersion = '${AppData.version}';` so it assigns the version to the new global name (and keep AppData.version unchanged), and ensure any other references to window.satusVersion in the codebase are updated to the new symbol (window.aliasVersion) to maintain consistency.package.json (1)
68-68:⚠️ Potential issue | 🟡 MinorRemove the unused
@hubspot/api-clientdependency.This dependency is not actively used—the only import is a type-only import in
lib/integrations/hubspot/fetch-form.ts, which explicitly uses raw fetch instead. HubSpot integration has been replaced by Twenty CRM throughout the codebase and is marked as deprecated. BothREBRANDING-SUMMARY.mdandMIGRATION.mddocument the removal of this dependency as a cleanup task.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@package.json` at line 68, Remove the unused HubSpot client dependency from package.json by deleting the "@hubspot/api-client": "^13.4.0" entry, remove the only type-only import in lib/integrations/hubspot/fetch-form.ts (e.g., the "import type ..." referencing '@hubspot/api-client'), and run your package manager to update lockfiles (npm/yarn/pnpm) so the lockfile no longer references the package; ensure no other files import symbols from '@hubspot/api-client' before committing.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@components/layout/footer/index.tsx`:
- Around line 6-8: The Tailwind classes in the footer div need to be sorted per
Biome rules: update the className string in the div inside
components/layout/footer/index.tsx (the element currently using "text-secondary
text-lg font-bold tracking-wider") to a Biome-sorted order, e.g., "font-bold
text-lg text-secondary tracking-wider", so the classes are alphabetically
ordered and pass static analysis.
In `@MIGRATION.md`:
- Around line 259-260: Update the "Migration Status" line under the "Last
Updated" header so it no longer asserts "Core integration complete" without
evidence: either change the status to something like "Implementation complete —
pending verification" or append direct links to verification artifacts (test
reports, CI badges, Lighthouse/bundle-size reports) and exact verification
dates; modify the "Migration Status: ✅ Core integration complete, scaffolding
ready for custom development" text accordingly and ensure no fabricated metrics
are added.
- Line 7: The Markdown lacks required blank lines around headings and fenced
code blocks (e.g., the "Brand Changes" heading) and contains unlabeled fenced
code blocks; fix by inserting a blank line before and after each heading and
each fenced code block and add an explicit language tag to every unlabeled
fenced block (choose appropriate languages like bash, json, or text based on
content). Update the "Brand Changes" section and any other headings/fenced
blocks listed in the review to follow this pattern so all headings and fenced
blocks have surrounding blank lines and every ``` block includes a language
identifier.
In `@REBRANDING-SUMMARY.md`:
- Around line 235-237: The summary makes definitive claims ("Migration Status: ✅
Complete", "Functionality: ✅ Preserved") without linked verification; update the
REBRANDING-SUMMARY.md entries for the "Migration Status", "Functionality", and
"Scaffolding" lines so they either include links to concrete verification
artifacts (test reports, CI run IDs, screenshots) or change the wording to a
softer state such as "Pending verification" or "Verified — see [link]" and
ensure each changed line references the verification artifact or uses the
suggested softer phrasing for clarity.
---
Outside diff comments:
In `@app/layout.tsx`:
- Around line 98-99: The version tracking script still sets window.satusVersion
using AppData.version; update the key to the new brand (e.g.,
window.aliasVersion) to match the rebrand. Modify the Script component that
contains `window.satusVersion = '${AppData.version}';` so it assigns the version
to the new global name (and keep AppData.version unchanged), and ensure any
other references to window.satusVersion in the codebase are updated to the new
symbol (window.aliasVersion) to maintain consistency.
In `@ARCHITECTURE.md`:
- Line 138: Update the footer attribution in ARCHITECTURE.md where the string
"*Built with [Satūs](https://github.com/darkroomengineering/satus) by
[darkroom.engineering](https://darkroom.engineering)*" appears: replace the
"Satūs" text and link and the "darkroom.engineering" text and link with the
Alias branding and appropriate Alias repository/website URL so the footer
matches the rest of the project; ensure the new text and links use the same
markdown formatting and phrasing as the original footer.
In `@package.json`:
- Line 68: Remove the unused HubSpot client dependency from package.json by
deleting the "@hubspot/api-client": "^13.4.0" entry, remove the only type-only
import in lib/integrations/hubspot/fetch-form.ts (e.g., the "import type ..."
referencing '@hubspot/api-client'), and run your package manager to update
lockfiles (npm/yarn/pnpm) so the lockfile no longer references the package;
ensure no other files import symbols from '@hubspot/api-client' before
committing.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
Run ID: fe92629c-1e99-476f-bf26-b83c076e7e09
📒 Files selected for processing (13)
ARCHITECTURE.mdCLAUDE.mdMIGRATION.mdPATTERNS.mdREADME.mdREBRANDING-SUMMARY.mdapp/(marketing)/_sections/getting-started/index.tsxapp/(marketing)/_sections/hero/index.tsxapp/layout.tsxcomponents/layout/footer/index.tsxcomponents/layout/header/index.tsxlib/integrations/twenty/client.tspackage.json
| <div className="text-secondary text-lg font-bold tracking-wider"> | ||
| ALIAS | ||
| </div> |
There was a problem hiding this comment.
Sort Tailwind CSS classes as required by Biome.
Static analysis flagged unsorted classes. As per coding guidelines, Tailwind classes must be sorted (Biome enforces this).
🔧 Suggested fix
- <div className="text-secondary text-lg font-bold tracking-wider">
+ <div className="text-lg font-bold tracking-wider text-secondary">📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| <div className="text-secondary text-lg font-bold tracking-wider"> | |
| ALIAS | |
| </div> | |
| <div className="text-lg font-bold tracking-wider text-secondary"> | |
| ALIAS | |
| </div> |
🧰 Tools
🪛 Biome (2.4.10)
[error] 6-6: These CSS classes should be sorted.
(lint/nursery/useSortedClasses)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@components/layout/footer/index.tsx` around lines 6 - 8, The Tailwind classes
in the footer div need to be sorted per Biome rules: update the className string
in the div inside components/layout/footer/index.tsx (the element currently
using "text-secondary text-lg font-bold tracking-wider") to a Biome-sorted
order, e.g., "font-bold text-lg text-secondary tracking-wider", so the classes
are alphabetically ordered and pass static analysis.
|
|
||
| ## Rebranding Summary | ||
|
|
||
| ### Brand Changes |
There was a problem hiding this comment.
Resolve markdownlint structure warnings in this doc.
Please add required blank lines around headings/fenced blocks and set a language for unlabeled fenced code blocks (for example the block starting at Line 31).
Also applies to: 13-13, 25-25, 30-31, 38-40, 45-45, 51-51, 109-109, 122-122, 135-135, 150-150, 168-168, 178-178, 189-189, 200-200
🧰 Tools
🪛 markdownlint-cli2 (0.22.0)
[warning] 7-7: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@MIGRATION.md` at line 7, The Markdown lacks required blank lines around
headings and fenced code blocks (e.g., the "Brand Changes" heading) and contains
unlabeled fenced code blocks; fix by inserting a blank line before and after
each heading and each fenced code block and add an explicit language tag to
every unlabeled fenced block (choose appropriate languages like bash, json, or
text based on content). Update the "Brand Changes" section and any other
headings/fenced blocks listed in the review to follow this pattern so all
headings and fenced blocks have surrounding blank lines and every ``` block
includes a language identifier.
| **Last Updated**: 2026-04-08 | ||
| **Migration Status**: ✅ Core integration complete, scaffolding ready for custom development |
There was a problem hiding this comment.
Soften “core integration complete” unless verification artifacts are linked.
This status line is a strong claim; either link explicit validation evidence or mark it as implementation-complete pending verification.
Based on learnings: Never fabricate measurements: do not invent Lighthouse scores, bundle sizes, or test results.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@MIGRATION.md` around lines 259 - 260, Update the "Migration Status" line
under the "Last Updated" header so it no longer asserts "Core integration
complete" without evidence: either change the status to something like
"Implementation complete — pending verification" or append direct links to
verification artifacts (test reports, CI badges, Lighthouse/bundle-size reports)
and exact verification dates; modify the "Migration Status: ✅ Core integration
complete, scaffolding ready for custom development" text accordingly and ensure
no fabricated metrics are added.
| **Migration Status**: ✅ Complete | ||
| **Functionality**: ✅ Preserved - All forms and features work with new branding | ||
| **Scaffolding**: ✅ Ready for custom Twenty CRM development |
There was a problem hiding this comment.
Avoid definitive functionality claims without linked verification evidence.
These statements read as fully validated results, but no test artifact/check output is attached in this file. Please either link concrete verification evidence or soften wording to “pending verification”.
Suggested wording adjustment
-**Migration Status**: ✅ Complete
-**Functionality**: ✅ Preserved - All forms and features work with new branding
+**Migration Status**: ✅ Implementation complete (verification checklist pending)
+**Functionality**: ⚠️ Pending verification in target environments📝 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.
| **Migration Status**: ✅ Complete | |
| **Functionality**: ✅ Preserved - All forms and features work with new branding | |
| **Scaffolding**: ✅ Ready for custom Twenty CRM development | |
| **Migration Status**: ✅ Implementation complete (verification checklist pending) | |
| **Functionality**: ⚠️ Pending verification in target environments | |
| **Scaffolding**: ✅ Ready for custom Twenty CRM development |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@REBRANDING-SUMMARY.md` around lines 235 - 237, The summary makes definitive
claims ("Migration Status: ✅ Complete", "Functionality: ✅ Preserved") without
linked verification; update the REBRANDING-SUMMARY.md entries for the "Migration
Status", "Functionality", and "Scaffolding" lines so they either include links
to concrete verification artifacts (test reports, CI run IDs, screenshots) or
change the wording to a softer state such as "Pending verification" or "Verified
— see [link]" and ensure each changed line references the verification artifact
or uses the suggested softer phrasing for clarity.
Add AI API routes, configuration, and demo chat page for CRM tools
There was a problem hiding this comment.
Actionable comments posted: 12
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@app/`(examples)/chat/page.module.css:
- Around line 132-137: The CSS currently transitions non-approved properties
(e.g., "transition: border-color 0.2s;" inside the .input rule and any
"transition: all ..." occurrences); update these to only animate transform and
opacity by replacing those declarations with something like "transition:
transform 0.2s, opacity 0.2s;" and remove any border-color/all transitions in
the .input and .input:focus rules (and the other occurrence noted around
204-210) so only transform/opacity are animated.
- Line 91: Replace the non-standard CSS property by changing "word-break:
break-word" to the standard "overflow-wrap: break-word" (or "overflow-wrap:
anywhere" if more aggressive breaking is desired) for the affected selector, and
remove/replace the transitions that animate properties other than transform and
opacity by updating the rules using "transition: border-color" and "transition:
all" to explicitly only animate transform and opacity (e.g., "transition:
transform 200ms ease, opacity 200ms ease" or "transition-property: transform,
opacity" with existing timing), ensuring no border-color or other property
animations remain.
In `@app/`(examples)/chat/page.tsx:
- Around line 77-83: Replace the template-string class composition on the
message wrapper with the project's cn() helper: import cn from 'clsx' (or ensure
cn is already imported) and change the className on the element that currently
uses `${s.message} ${message.role === 'user' ? s.messageUser :
s.messageAssistant}` to use cn(s.message, message.role === 'user' ?
s.messageUser : s.messageAssistant) so CSS module classes are composed via cn;
update the same pattern wherever message.role conditional classes
(s.messageUser/s.messageAssistant) are used.
- Around line 120-127: Add a programmatic label for the message input by giving
the input an id (e.g., message-input) and adding a corresponding <label
htmlFor="message-input">Message</label> (or use aria-label/aria-labelledby) so
the field used by value={input}, onChange={(e) => setInput(e.target.value)},
className={s.input} and disabled={isLoading} is accessible to screen readers;
ensure the label text is descriptive (e.g., "Message") and keep the existing
placeholder and behavior intact.
- Around line 83-90: The map over message.parts currently uses the array index
as the React key (in message.parts.map and the returned JSX for both text and
tool-invocation branches); replace this with a stable identifier—e.g., build a
composite key using the parent message ID plus the part position (like
`${message.id}-${index}`) or, better, update the API to return a unique part id
and use part.id—so change the key props on the span/div (references:
message.parts.map, part, message.id, classNames s.toolCall and s.toolName) to
use that stable id instead of the raw index.
In `@app/api/ai/agent/route.ts`:
- Around line 55-67: Add a Zod discriminated-union request schema and validate
the incoming body before branching so exactly one of messages or prompt is
present; define a schema (e.g., z.union of two objects: { type: "chat",
messages: z.array(...).nonempty(), userId?: z.string(), context?: z.any() } and
{ type: "prompt", prompt: z.string().nonempty(), userId?: z.string(), context?:
z.any() }) or use z.discriminatedUnion with an explicit discriminator, then
parse await req.json() against it in the route handler and use the parsed result
to decide the branch (pass parsed.messages to createAgentUIStreamResponse with
uiMessages and parsed.userId/parsed.context, or pass parsed.prompt to
crmAgent.generate or other prompt flow); return a 400 with the Zod error message
when validation fails instead of relying on truthy checks.
In `@app/api/ai/generate/route.ts`:
- Around line 23-33: The handler currently accepts a freeform model string and
uses an incorrect import path; fix by validating the full request payload with a
schema (e.g., use zod to parse { prompt, system?, model? } ensuring prompt is a
nonempty string and model is optional but must be one of an allowed-model
enum/whitelist), default to DEFAULT_CHAT_MODEL when model is absent, and pass
the validated values into generateText (referencing generateText,
DEFAULT_CHAT_MODEL, and SYSTEM_PROMPTS); also update the import from "@/ai" to
"@/lib/ai".
In `@app/api/ai/structured/route.ts`:
- Around line 65-72: Replace the ad-hoc validation in the request handler with a
Zod runtime schema: define a requestSchema (using z.object) that requires prompt
as non-empty string, schema as a z.enum built from the keys of the existing
schemas map (typed as SchemaKey), and an optional model string; then call
requestSchema.parse(await req.json()) to destructure { prompt, schema, model }
and remove the manual typeof checks and the cast to SchemaKey so the handler
uses validated values (reference the variables requestSchema, schemas, and
SchemaKey in route.ts).
In `@lib/ai/README.md`:
- Around line 19-31: The top-level fenced directory block in README.md is
missing a language identifier and should be changed to a fenced block with a
language (e.g., ```text) to satisfy MD040; also the nested fenced snippet under
the "Message conversion" section around the example using convertToModelMessages
must have blank lines before and after the fenced block to satisfy MD031. Edit
the README.md entries: update the initial ``` to ```text for the lib/ai/ tree
block, and insert a blank line before and after the ```ts block that contains
"messages: await convertToModelMessages(messages)" so the surrounding paragraphs
are separated from the fenced code.
- Around line 124-131: The README references AI_GATEWAY_API_KEY but the
environment schema is missing it; update the envSchema (the envSchema zod/object
definition and exported env parsing) to include AI_GATEWAY_API_KEY (e.g., add a
string entry like AI_GATEWAY_API_KEY: z.string().optional() or nonempty()
depending on intended validation) alongside TWENTY_API_URL and TWENTY_API_KEY so
it is validated and present in the exported Env type and runtime env object used
throughout the codebase.
In `@lib/ai/tools.ts`:
- Line 46: Replace the unused catch bindings in lib/ai/tools.ts by removing the
identifier so the blocks use an anonymous catch (i.e., change "catch (error) {
... }" to "catch { ... }") for each occurrence (the catch at lines shown: 46,
89, 124, 152); update the bodies unchanged so they no longer refer to the
removed variable. This removes the unused variable and satisfies the Biome
noUnusedVariables rule.
- Around line 206-212: The current evaluator uses new Function on the sanitized
expression (variable sanitized) which is unsafe and the Zod schema lacks length
limits; replace the new Function(`return ${sanitized}`)() usage with a safe math
evaluator (for example integrate mathjs or a small validated parser) and add
pre-evaluation checks for maximum expression length and nesting depth (validate
parentheses depth and token count) to prevent DoS; also update the Zod schema
that validates the incoming expression to include a max length constraint (e.g.,
max length N) so overly long inputs are rejected before reaching the evaluator.
Ensure errors return the existing { error: ... } shape when validation fails.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
Run ID: d44bee91-f5e2-4aa7-9ddc-fc87015084b2
⛔ Files ignored due to path filters (1)
bun.lockis excluded by!**/*.lock
📒 Files selected for processing (13)
app/(examples)/chat/page.module.cssapp/(examples)/chat/page.tsxapp/(marketing)/_sections/getting-started/index.tsxapp/api/ai/agent/route.tsapp/api/ai/chat/route.tsapp/api/ai/generate/route.tsapp/api/ai/structured/route.tscomponents/layout/header/index.tsxlib/ai/README.mdlib/ai/config.tslib/ai/index.tslib/ai/tools.tspackage.json
| font-size: 0.9375rem; | ||
| line-height: 1.5; | ||
| white-space: pre-wrap; | ||
| word-break: break-word; |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
cat -n app/\(examples\)/chat/page.module.css | sed -n '85,100p'Repository: danmarauda/satus
Length of output: 459
🏁 Script executed:
cat -n app/\(examples\)/chat/page.module.css | sed -n '125,145p'Repository: danmarauda/satus
Length of output: 599
🏁 Script executed:
cat -n app/\(examples\)/chat/page.module.css | sed -n '195,215p'Repository: danmarauda/satus
Length of output: 498
Refactor to limit animations to transform and opacity only; replace non-standard word-break property.
Line 91 uses non-standard word-break: break-word; use overflow-wrap instead. Additionally, lines 132 and 204 violate animation constraints: transition: border-color and transition: all animate properties beyond the allowed transform and opacity. Remove or replace these transitions.
Proposed fixes
.messageContent {
font-size: 0.9375rem;
line-height: 1.5;
white-space: pre-wrap;
- word-break: break-word;
+ overflow-wrap: anywhere;
+ word-break: normal;
} .input {
padding: 0.75rem 1rem;
font-size: 1rem;
border: 1px solid var(--color-border);
border-radius: 0.5rem;
background: var(--color-background);
color: var(--color-foreground);
outline: none;
- transition: border-color 0.2s;
} .suggestion {
padding: 0.5rem 1rem;
font-size: 0.875rem;
border: 1px solid var(--color-border);
border-radius: 2rem;
background: transparent;
color: var(--color-foreground);
cursor: pointer;
- transition: all 0.2s;
}🧰 Tools
🪛 Stylelint (17.6.0)
[error] 91-91: Unexpected deprecated keyword "break-word" for property "word-break" (declaration-property-value-keyword-no-deprecated)
(declaration-property-value-keyword-no-deprecated)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@app/`(examples)/chat/page.module.css at line 91, Replace the non-standard CSS
property by changing "word-break: break-word" to the standard "overflow-wrap:
break-word" (or "overflow-wrap: anywhere" if more aggressive breaking is
desired) for the affected selector, and remove/replace the transitions that
animate properties other than transform and opacity by updating the rules using
"transition: border-color" and "transition: all" to explicitly only animate
transform and opacity (e.g., "transition: transform 200ms ease, opacity 200ms
ease" or "transition-property: transform, opacity" with existing timing),
ensuring no border-color or other property animations remain.
| transition: border-color 0.2s; | ||
| } | ||
|
|
||
| .input:focus { | ||
| border-color: var(--color-primary); | ||
| } |
There was a problem hiding this comment.
🛠️ Refactor suggestion | 🟠 Major
Restrict CSS animations to transform and opacity only.
Current transitions animate non-approved properties (border-color, all).
Proposed fix
.input {
@@
- transition: border-color 0.2s;
+ transition: opacity 0.2s;
}
@@
.suggestion {
@@
- transition: all 0.2s;
+ transition: transform 0.2s, opacity 0.2s;
}
.suggestion:hover {
background: var(--color-muted);
border-color: var(--color-primary);
+ transform: translateY(-1px);
+ opacity: 0.95;
}As per coding guidelines "**/*.{module.css,css}: Animate only 'transform' and 'opacity' CSS properties".
Also applies to: 204-210
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@app/`(examples)/chat/page.module.css around lines 132 - 137, The CSS
currently transitions non-approved properties (e.g., "transition: border-color
0.2s;" inside the .input rule and any "transition: all ..." occurrences); update
these to only animate transform and opacity by replacing those declarations with
something like "transition: transform 0.2s, opacity 0.2s;" and remove any
border-color/all transitions in the .input and .input:focus rules (and the other
occurrence noted around 204-210) so only transform/opacity are animated.
| className={`${s.message} ${ | ||
| message.role === 'user' ? s.messageUser : s.messageAssistant | ||
| }`} | ||
| > | ||
| <span className={s.messageRole}>{message.role}</span> | ||
| <div className={s.messageContent}> | ||
| {message.parts.map((part, index) => { |
There was a problem hiding this comment.
🛠️ Refactor suggestion | 🟠 Major
Use cn() for CSS module + conditional class composition.
This segment uses template-string composition instead of cn(), which diverges from repository rules.
Proposed fix
+import cn from 'clsx'
@@
- className={`${s.message} ${
- message.role === 'user' ? s.messageUser : s.messageAssistant
- }`}
+ className={cn(
+ s.message,
+ message.role === 'user' ? s.messageUser : s.messageAssistant
+ )}As per coding guidelines "**/*.{ts,tsx}: Import CSS Modules and use cn() from clsx to combine class names".
📝 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.
| className={`${s.message} ${ | |
| message.role === 'user' ? s.messageUser : s.messageAssistant | |
| }`} | |
| > | |
| <span className={s.messageRole}>{message.role}</span> | |
| <div className={s.messageContent}> | |
| {message.parts.map((part, index) => { | |
| className={cn( | |
| s.message, | |
| message.role === 'user' ? s.messageUser : s.messageAssistant | |
| )} | |
| > | |
| <span className={s.messageRole}>{message.role}</span> | |
| <div className={s.messageContent}> | |
| {message.parts.map((part, index) => { |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@app/`(examples)/chat/page.tsx around lines 77 - 83, Replace the
template-string class composition on the message wrapper with the project's cn()
helper: import cn from 'clsx' (or ensure cn is already imported) and change the
className on the element that currently uses `${s.message} ${message.role ===
'user' ? s.messageUser : s.messageAssistant}` to use cn(s.message, message.role
=== 'user' ? s.messageUser : s.messageAssistant) so CSS module classes are
composed via cn; update the same pattern wherever message.role conditional
classes (s.messageUser/s.messageAssistant) are used.
| {message.parts.map((part, index) => { | ||
| if (part.type === 'text') { | ||
| return <span key={index}>{part.text}</span> | ||
| } | ||
| if (part.type === 'tool-invocation') { | ||
| return ( | ||
| <div key={index} className={s.toolCall}> | ||
| <span className={s.toolName}> |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
# First, locate and examine the file
fd -t f "page.tsx" | grep -E "app/(examples)?/chat"Repository: danmarauda/satus
Length of output: 42
🏁 Script executed:
# Read the specific file and check lines around 83-90
cat -n "app/(examples)/chat/page.tsx" | sed -n '75,100p'Repository: danmarauda/satus
Length of output: 1522
🏁 Script executed:
# Check the full structure to understand context better
wc -l "app/(examples)/chat/page.tsx"Repository: danmarauda/satus
Length of output: 92
🏁 Script executed:
# Check if file has 'use client' directive and imports
head -20 "app/(examples)/chat/page.tsx"Repository: danmarauda/satus
Length of output: 643
🏁 Script executed:
# Look for the input element mentioned in the scratchpad (around lines 120-127)
sed -n '115,130p' "app/(examples)/chat/page.tsx" | cat -nRepository: danmarauda/satus
Length of output: 688
🏁 Script executed:
# Check for cn() or clsx usage in the file
rg "from.*clsx|from.*cn|import.*cn|import.*clsx" "app/(examples)/chat/page.tsx"Repository: danmarauda/satus
Length of output: 42
🏁 Script executed:
# Check the exact structure around line 77 to see the full className usage
sed -n '70,110p' "app/(examples)/chat/page.tsx" | cat -nRepository: danmarauda/satus
Length of output: 2042
🏁 Script executed:
# Check if cn or clsx is imported from anywhere in the file
rg "cn\(|clsx\(" "app/(examples)/chat/page.tsx"Repository: danmarauda/satus
Length of output: 42
🏁 Script executed:
# Check the module.css file to understand the CSS classes
cat -n "app/(examples)/chat/page.module.css" 2>/dev/null || echo "CSS module file not found, checking for alternatives"Repository: danmarauda/satus
Length of output: 5099
🏁 Script executed:
# Verify the data structure - check if parts have stable identifiers
sed -n '1,141p' "app/(examples)/chat/page.tsx" | grep -A5 -B5 "toolCallId\|toolInvocation" | head -30Repository: danmarauda/satus
Length of output: 884
🏁 Script executed:
# Verify the structure of the parts array - look for type definitions or usage patterns
rg -A 5 "parts.map|part.type|toolInvocation" "app/(examples)/chat/page.tsx" | head -50Repository: danmarauda/satus
Length of output: 1060
🏁 Script executed:
# Check if there's a type definition or API response structure showing what toolCallId might be
fd -t f "\.ts$|\.tsx$|\.d\.ts$" | xargs rg "toolCallId|ToolInvocation" 2>/dev/null | head -20Repository: danmarauda/satus
Length of output: 42
🏁 Script executed:
# Look for where the message.parts data comes from to understand the data structure
sed -n '1,50p' "app/(examples)/chat/page.tsx" | grep -A 20 "useChat\|transport\|sendMessage"Repository: danmarauda/satus
Length of output: 1371
🏁 Script executed:
# Check if toolCallId exists in the actual usage or is documented
rg "toolCallId" "app/(examples)/chat/page.tsx"Repository: danmarauda/satus
Length of output: 42
🏁 Script executed:
# Look at the ai-sdk documentation or type definitions that might be available
fd -t f "package.json" | xargs cat | grep -A 5 "@ai-sdk\|@vercel/ai\|ai"Repository: danmarauda/satus
Length of output: 1062
🏁 Script executed:
# Check if there's a lock file to understand the exact version
fd "yarn.lock|package-lock.json|bun.lockb" | head -1 | xargs -I {} sh -c 'file {} && (head -20 {} || true)'Repository: danmarauda/satus
Length of output: 42
🏁 Script executed:
# Verify the actual structure by searching for any type definitions or interfaces
fd -t f "types.ts|index.ts|page.tsx" app/ | xargs rg -l "toolInvocation|TextPart|ToolInvocationPart" 2>/dev/nullRepository: danmarauda/satus
Length of output: 88
🏁 Script executed:
# Check what properties exist on the toolInvocation object by searching across the codebase
rg "toolInvocation\." "app/(examples)/chat/page.tsx" | sed 's/.*toolInvocation\./toolInvocation./' | sort -uRepository: danmarauda/satus
Length of output: 156
🏁 Script executed:
# Look for clsx import and usage in the codebase to understand the convention
fd -t f "\.tsx$" -path "app/*" | xargs rg "import.*clsx|from.*clsx" 2>/dev/null | head -10Repository: danmarauda/satus
Length of output: 778
🏁 Script executed:
# Check if cn() is defined somewhere as a utility
fd -t f "cn\\.ts$|cn\\.js$" | xargs cat 2>/dev/nullRepository: danmarauda/satus
Length of output: 42
🏁 Script executed:
# Search for how other components combine CSS Module classes
rg "className=.*s\\." "app/" -A 1 -B 1 | head -30Repository: danmarauda/satus
Length of output: 2044
🏁 Script executed:
# Check the import statement at the top of chat/page.tsx to see if clsx/cn is imported
head -10 "app/(examples)/chat/page.tsx"Repository: danmarauda/satus
Length of output: 331
🏁 Script executed:
# Verify exactly what properties are available on parts by checking the structure
sed -n '83,110p' "app/(examples)/chat/page.tsx" | cat -nRepository: danmarauda/satus
Length of output: 1463
🏁 Script executed:
# Check if there's any documentation or usage of toolCallId elsewhere
rg "toolCallId\|toolCall\.id\|part\\.id" --type tsx --type tsRepository: danmarauda/satus
Length of output: 87
🏁 Script executed:
# Search for how to properly generate unique keys for parts - check if there's an id field
rg "part\." "app/(examples)/chat/page.tsx" -o | sort -uRepository: danmarauda/satus
Length of output: 65
🏁 Script executed:
# Check the AI SDK documentation or examples in the codebase for part structure
fd -type f -name "*.tsx" -o -name "*.ts" | xargs rg "message.parts|MessagePart" 2>/dev/null | head -20Repository: danmarauda/satus
Length of output: 334
🏁 Script executed:
# Double-check what cn/clsx is being used elsewhere and verify the exact import pattern
rg "import cn from 'clsx'" "app/" | head -5Repository: danmarauda/satus
Length of output: 399
🏁 Script executed:
# Search for how className template strings are handled in this file
sed -n '70,85p' "app/(examples)/chat/page.tsx" | cat -nRepository: danmarauda/satus
Length of output: 835
🏁 Script executed:
# Verify if there's any unique identifier on the part object that we haven't seen yet
sed -n '83,105p' "app/(examples)/chat/page.tsx"Repository: danmarauda/satus
Length of output: 1143
🏁 Script executed:
# Check the API to understand the structure better - look at the useChat hook documentation
rg -A 30 "transport: new DefaultChatTransport" "app/(examples)/chat/page.tsx"Repository: danmarauda/satus
Length of output: 900
Replace array-index keys with stable part identifiers.
Using index as key (lines 85 and 89) violates Biome's noArrayIndexKey rule and risks incorrect UI reconciliation during streaming updates. However, the parts structure lacks a built-in unique identifier—part.toolInvocation contains only toolName, state, and output. Consider generating a stable key from the message ID and part position, or refactor the API response to include unique part identifiers.
🧰 Tools
🪛 Biome (2.4.10)
[error] 85-85: Avoid using the index of an array as key property in an element.
(lint/suspicious/noArrayIndexKey)
[error] 89-89: Avoid using the index of an array as key property in an element.
(lint/suspicious/noArrayIndexKey)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@app/`(examples)/chat/page.tsx around lines 83 - 90, The map over
message.parts currently uses the array index as the React key (in
message.parts.map and the returned JSX for both text and tool-invocation
branches); replace this with a stable identifier—e.g., build a composite key
using the parent message ID plus the part position (like
`${message.id}-${index}`) or, better, update the API to return a unique part id
and use part.id—so change the key props on the span/div (references:
message.parts.map, part, message.id, classNames s.toolCall and s.toolName) to
use that stable id instead of the raw index.
| <input | ||
| type="text" | ||
| value={input} | ||
| onChange={(e) => setInput(e.target.value)} | ||
| placeholder="Type your message..." | ||
| className={s.input} | ||
| disabled={isLoading} | ||
| /> |
There was a problem hiding this comment.
Add an explicit label for the message input.
The text input relies on placeholder only; add a programmatic label for accessibility.
Proposed fix
- <form onSubmit={handleSubmit} className={s.inputForm}>
+ <form onSubmit={handleSubmit} className={s.inputForm}>
+ <label htmlFor="chat-input" className="sr-only">
+ Message
+ </label>
<input
+ id="chat-input"
type="text"
value={input}📝 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.
| <input | |
| type="text" | |
| value={input} | |
| onChange={(e) => setInput(e.target.value)} | |
| placeholder="Type your message..." | |
| className={s.input} | |
| disabled={isLoading} | |
| /> | |
| <label htmlFor="chat-input" className="sr-only"> | |
| Message | |
| </label> | |
| <input | |
| id="chat-input" | |
| type="text" | |
| value={input} | |
| onChange={(e) => setInput(e.target.value)} | |
| placeholder="Type your message..." | |
| className={s.input} | |
| disabled={isLoading} | |
| /> |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@app/`(examples)/chat/page.tsx around lines 120 - 127, Add a programmatic
label for the message input by giving the input an id (e.g., message-input) and
adding a corresponding <label htmlFor="message-input">Message</label> (or use
aria-label/aria-labelledby) so the field used by value={input}, onChange={(e) =>
setInput(e.target.value)}, className={s.input} and disabled={isLoading} is
accessible to screen readers; ensure the label text is descriptive (e.g.,
"Message") and keep the existing placeholder and behavior intact.
| const { prompt, schema, model } = await req.json() | ||
|
|
||
| if (!prompt || typeof prompt !== 'string') { | ||
| return Response.json({ error: 'Prompt is required' }, { status: 400 }) | ||
| } | ||
|
|
||
| const selectedSchema = schemas[schema as SchemaKey] | ||
| if (!selectedSchema) { |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
cat -n app/api/ai/structured/route.tsRepository: danmarauda/satus
Length of output: 3507
Use a Zod request schema to validate the request body instead of type casting.
The request handler destructures prompt, schema, and model from req.json() without proper validation. Only prompt is partially checked; schema is type-cast (as SchemaKey) without runtime validation, and model is completely unconstrained. Per coding guidelines, "Use Zod schemas for validation in all server actions and form inputs." Create a requestSchema to validate all three parameters:
const requestSchema = z.object({
prompt: z.string().min(1),
schema: z.enum(Object.keys(schemas) as [SchemaKey, ...SchemaKey[]]),
model: z.string().optional(),
})
const { prompt, schema, model } = requestSchema.parse(await req.json())This ensures type safety at runtime and prevents invalid schemas or unexpected model values from reaching the handler logic.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@app/api/ai/structured/route.ts` around lines 65 - 72, Replace the ad-hoc
validation in the request handler with a Zod runtime schema: define a
requestSchema (using z.object) that requires prompt as non-empty string, schema
as a z.enum built from the keys of the existing schemas map (typed as
SchemaKey), and an optional model string; then call requestSchema.parse(await
req.json()) to destructure { prompt, schema, model } and remove the manual
typeof checks and the cast to SchemaKey so the handler uses validated values
(reference the variables requestSchema, schemas, and SchemaKey in route.ts).
| ``` | ||
| lib/ai/ | ||
| ├── index.ts # Central exports | ||
| ├── config.ts # Models, system prompts, configuration | ||
| ├── tools.ts # Tool definitions for agents | ||
| └── README.md # This file | ||
|
|
||
| app/api/ai/ | ||
| ├── chat/route.ts # Streaming chat endpoint | ||
| ├── generate/route.ts # Text generation endpoint | ||
| ├── structured/route.ts # Structured output endpoint | ||
| └── agent/route.ts # CRM agent with tools | ||
| ``` |
There was a problem hiding this comment.
Fix markdownlint violations in fenced code blocks.
The fence at Line 19 lacks a language (MD040), and the nested fence around Line 168 needs blank lines around it (MD031).
Proposed doc fix
-```
+```text
lib/ai/
├── index.ts # Central exports
├── config.ts # Models, system prompts, configuration
├── tools.ts # Tool definitions for agents
└── README.md # This file
@@
-2. **Message conversion**: When using `useChat`, always convert messages before passing to `streamText`:
- ```ts
- messages: await convertToModelMessages(messages)
- ```
+2. **Message conversion**: When using `useChat`, always convert messages before passing to `streamText`:
+
+ ```ts
+ messages: await convertToModelMessages(messages)
+ ```Also applies to: 168-170
🧰 Tools
🪛 markdownlint-cli2 (0.22.0)
[warning] 19-19: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@lib/ai/README.md` around lines 19 - 31, The top-level fenced directory block
in README.md is missing a language identifier and should be changed to a fenced
block with a language (e.g., ```text) to satisfy MD040; also the nested fenced
snippet under the "Message conversion" section around the example using
convertToModelMessages must have blank lines before and after the fenced block
to satisfy MD031. Edit the README.md entries: update the initial ``` to ```text
for the lib/ai/ tree block, and insert a blank line before and after the ```ts
block that contains "messages: await convertToModelMessages(messages)" so the
surrounding paragraphs are separated from the fenced code.
| ```env | ||
| # Required for non-zero-config providers (xAI, Groq) | ||
| AI_GATEWAY_API_KEY=your_key_here | ||
|
|
||
| # Optional: Twenty CRM for tool integration | ||
| TWENTY_API_URL=https://api.twenty.com | ||
| TWENTY_API_KEY=your_twenty_key | ||
| ``` |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Verify whether AI_GATEWAY_API_KEY is declared/used in the repo
rg -n -C2 "AI_GATEWAY_API_KEY|TWENTY_API_URL|TWENTY_API_KEY|envSchema|z\\.object\\(" --type ts --type tsxRepository: danmarauda/satus
Length of output: 87
🏁 Script executed:
#!/bin/bash
# Verify environment variables used in the repo (corrected)
rg -n -C2 "AI_GATEWAY_API_KEY|TWENTY_API_URL|TWENTY_API_KEY|envSchema|z\.object" --type tsRepository: danmarauda/satus
Length of output: 15810
🏁 Script executed:
#!/bin/bash
# Also check lib/env.ts directly to see the schema
cat -n lib/env.ts | head -100Repository: danmarauda/satus
Length of output: 2963
🏁 Script executed:
#!/bin/bash
# Search for any AI_GATEWAY or GATEWAY references
rg -i "gateway|AI_GATEWAY" --type ts --type jsonRepository: danmarauda/satus
Length of output: 826
🏁 Script executed:
#!/bin/bash
# Search for actual usage of AI_GATEWAY_API_KEY (not just comments/docs)
rg "AI_GATEWAY_API_KEY" --type ts --type json -B 2 -A 2Repository: danmarauda/satus
Length of output: 301
🏁 Script executed:
#!/bin/bash
# Check lib/ai/config.ts to understand how Vercel AI Gateway is configured
cat -n lib/ai/config.tsRepository: danmarauda/satus
Length of output: 2850
Add AI_GATEWAY_API_KEY to the environment schema for consistency.
The README documents AI_GATEWAY_API_KEY as required for xAI and Groq providers, but it's not defined in lib/env.ts like other integration variables (TWENTY_API_URL, TWENTY_API_KEY). This inconsistency can mislead users during setup. Add AI_GATEWAY_API_KEY to the envSchema in lib/env.ts to align with the documented requirements.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@lib/ai/README.md` around lines 124 - 131, The README references
AI_GATEWAY_API_KEY but the environment schema is missing it; update the
envSchema (the envSchema zod/object definition and exported env parsing) to
include AI_GATEWAY_API_KEY (e.g., add a string entry like AI_GATEWAY_API_KEY:
z.string().optional() or nonempty() depending on intended validation) alongside
TWENTY_API_URL and TWENTY_API_KEY so it is validated and present in the exported
Env type and runtime env object used throughout the codebase.
| contacts: result.persons || [], | ||
| count: result.persons?.length || 0, | ||
| } | ||
| } catch (error) { |
There was a problem hiding this comment.
Fix Biome noUnusedVariables errors in catch blocks.
These catch (error) bindings are unused and currently fail lint checks.
Suggested fix
- } catch (error) {
+ } catch {
return { error: 'Failed to search contacts', contacts: [] }
}
...
- } catch (error) {
+ } catch {
return { error: 'Failed to create contact', contact: null }
}
...
- } catch (error) {
+ } catch {
return { error: 'Failed to add note', note: null }
}
...
- } catch (error) {
+ } catch {
return { error: 'Failed to list companies', companies: [] }
}Also applies to: 89-89, 124-124, 152-152
🧰 Tools
🪛 Biome (2.4.10)
[error] 46-46: This variable error is unused.
(lint/correctness/noUnusedVariables)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@lib/ai/tools.ts` at line 46, Replace the unused catch bindings in
lib/ai/tools.ts by removing the identifier so the blocks use an anonymous catch
(i.e., change "catch (error) { ... }" to "catch { ... }") for each occurrence
(the catch at lines shown: 46, 89, 124, 152); update the bodies unchanged so
they no longer refer to the removed variable. This removes the unused variable
and satisfies the Biome noUnusedVariables rule.
| const sanitized = expression.replace(/[^0-9+\-*/().%\s]/g, '') | ||
| if (sanitized !== expression) { | ||
| return { error: 'Invalid characters in expression' } | ||
| } | ||
| // eslint-disable-next-line no-new-func | ||
| const result = new Function(`return ${sanitized}`)() | ||
| return { |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Verify dynamic code-evaluation call sites in the repo.
rg -nP --type=ts --type=tsx '\b(new Function|eval)\s*\('Repository: danmarauda/satus
Length of output: 87
🏁 Script executed:
cat -n lib/ai/tools.ts | sed -n '200,220p'Repository: danmarauda/satus
Length of output: 863
🏁 Script executed:
rg -n 'new Function|eval' --type ts lib/ai/tools.tsRepository: danmarauda/satus
Length of output: 190
🏁 Script executed:
rg -n 'new Function|eval' lib/ai/Repository: danmarauda/satus
Length of output: 222
🏁 Script executed:
cat package.json | jq '.dependencies, .devDependencies' | grep -i 'math\|parser\|eval'Repository: danmarauda/satus
Length of output: 42
🏁 Script executed:
cat package.json | head -80Repository: danmarauda/satus
Length of output: 3185
🏁 Script executed:
rg -B5 -A10 'execute: async.*expression' lib/ai/tools.tsRepository: danmarauda/satus
Length of output: 652
🏁 Script executed:
rg -B10 'expression.*string' lib/ai/tools.ts | head -30Repository: danmarauda/satus
Length of output: 42
🏁 Script executed:
rg -A5 'expression: z' lib/ai/tools.tsRepository: danmarauda/satus
Length of output: 230
Replace new Function with a safe math expression evaluator and add expression length limits.
The current sanitization is insufficient—it only removes disallowed characters but does not prevent denial-of-service attacks via pathologically complex expressions (e.g., deeply nested parentheses, extremely long inputs). Add a maximum length constraint to the Zod schema and replace new Function with a dedicated safe math evaluator.
Suggested approach
- expression: z
- .string()
- .describe('Mathematical expression (e.g., "2 + 2", "10 * 5")'),
+ expression: z
+ .string()
+ .max(120)
+ .describe('Mathematical expression (e.g., "2 + 2", "10 * 5")'),Replace new Function with a safe parser (consider math.js, mathjs, or a minimal custom parser that validates nesting depth and expression structure).
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@lib/ai/tools.ts` around lines 206 - 212, The current evaluator uses new
Function on the sanitized expression (variable sanitized) which is unsafe and
the Zod schema lacks length limits; replace the new Function(`return
${sanitized}`)() usage with a safe math evaluator (for example integrate mathjs
or a small validated parser) and add pre-evaluation checks for maximum
expression length and nesting depth (validate parentheses depth and token count)
to prevent DoS; also update the Zod schema that validates the incoming
expression to include a max length constraint (e.g., max length N) so overly
long inputs are rejected before reaching the evaluator. Ensure errors return the
existing { error: ... } shape when validation fails.
Add comprehensive "Explore" section to homepage and update header navigation.
There was a problem hiding this comment.
Actionable comments posted: 6
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@app/`(marketing)/_sections/explore/explore.module.css:
- Around line 100-102: The hover rule &:hover .cardAccent targets a non-existent
element; either remove the unused .cardAccent CSS blocks (including the other
occurrences flagged) or add an element with class="cardAccent" into the Explore
card component markup so the hover opacity change has an effect; locate the
&:hover .cardAccent selector in the explore.module.css and either delete those
rules or wire up the Explore card template to include a .cardAccent element
(ensuring any related styles like opacity and positioning are kept consistent).
- Around line 79-93: The rule currently uses "transition: all 0.3s ease" and
animates background and border-color in the "&:hover" block (non-compositor
properties); change this to animate only compositor properties: replace
"transition: all 0.3s ease" with a focused transition like "transition:
transform 0.3s ease" (and/or "opacity 0.3s ease"), remove direct
background/border-color transitions from the "&:hover" rule, and implement the
color overlay/border visuals via an absolutely positioned pseudo-element (or
separate element) whose opacity is toggled on hover so only transform/opacity
are animated; update the selector containing "min-height" and the "&:hover"
block accordingly.
- Around line 4-13: Replace raw viewport math in the explore.module.css padding
rules with the project's viewport helper functions: use mobile-vw() for the
mobile calc(...) values used in padding-top and padding-bottom and switch to
desktop-vw() for the values inside the `@media` (--desktop) block; keep the
existing background and border-top rule but ensure any remaining numeric
expressions that used calc(... / ... * 100vw) are converted to the corresponding
mobile-vw() or desktop-vw() helper calls so sizing follows the project's
token-driven helpers (reference: padding-top, padding-bottom, and the `@media`
(--desktop) block).
In `@app/`(marketing)/_sections/explore/index.tsx:
- Around line 10-17: Change the declared object-shaped types to interfaces:
replace the type alias "type Feature = { ... }" with "interface Feature { ... }"
and likewise convert the two inline prop object types used at the component prop
positions on lines 86 and 113 into named interfaces. Update any references to
those shapes to use the new interface names so the file follows the repo rule
"interface for object shapes, type for unions."
In `@app/page.tsx`:
- Line 11: Replace the relative import of the Explore component in app/page.tsx
(the line importing Explore from './(marketing)/_sections/explore') with the
project path-alias style; locate the import statement for Explore and update it
to use the '@/...' alias that matches your repo conventions (e.g., import
Explore from '@/...'), ensuring the module path points to the same
(marketing)/_sections/explore module via the alias.
In `@components/layout/header/index.tsx`:
- Line 10: The active-state logic currently uses usePathname() which strips hash
fragments so links like '/#explore' will never become active; update the header
component's active detection (where you map nav items including href
'/#explore') to also check the hash by comparing href's fragment to
window.location.hash for hash-based items (e.g., detect when href includes '#'
or startsWith '/#explore'), and guard against SSR by checking typeof window !==
'undefined' before reading window.location.hash; keep usePathname() for non-hash
links so both path and hash cases are handled.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
Run ID: 5de01aa2-bb5f-45a6-ba62-a574f3024b41
📒 Files selected for processing (4)
app/(marketing)/_sections/explore/explore.module.cssapp/(marketing)/_sections/explore/index.tsxapp/page.tsxcomponents/layout/header/index.tsx
| padding-top: calc(80 / 375 * 100vw); | ||
| padding-bottom: calc(80 / 375 * 100vw); | ||
| background: var(--color-primary); | ||
| border-top: 1px solid | ||
| color-mix(in srgb, var(--color-secondary) 15%, transparent); | ||
|
|
||
| @media (--desktop) { | ||
| padding-top: calc(120 / 1440 * 100vw); | ||
| padding-bottom: calc(120 / 1440 * 100vw); | ||
| } |
There was a problem hiding this comment.
🛠️ Refactor suggestion | 🟠 Major
Use project viewport helper functions instead of manual calc(...100vw).
Lines 4-13 use raw viewport math; this should use the project sizing helpers for consistency and token-driven responsiveness.
Proposed change (example)
.section {
@@
- padding-top: calc(80 / 375 * 100vw);
- padding-bottom: calc(80 / 375 * 100vw);
+ padding-top: mobile-vw(80);
+ padding-bottom: mobile-vw(80);
@@
`@media` (--desktop) {
- padding-top: calc(120 / 1440 * 100vw);
- padding-bottom: calc(120 / 1440 * 100vw);
+ padding-top: desktop-vw(120);
+ padding-bottom: desktop-vw(120);
}
}As per coding guidelines, "Use custom viewport functions (mobile-vw(), mobile-vh(), desktop-vw(), desktop-vh()) for responsive sizing".
📝 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.
| padding-top: calc(80 / 375 * 100vw); | |
| padding-bottom: calc(80 / 375 * 100vw); | |
| background: var(--color-primary); | |
| border-top: 1px solid | |
| color-mix(in srgb, var(--color-secondary) 15%, transparent); | |
| @media (--desktop) { | |
| padding-top: calc(120 / 1440 * 100vw); | |
| padding-bottom: calc(120 / 1440 * 100vw); | |
| } | |
| padding-top: mobile-vw(80); | |
| padding-bottom: mobile-vw(80); | |
| background: var(--color-primary); | |
| border-top: 1px solid | |
| color-mix(in srgb, var(--color-secondary) 15%, transparent); | |
| `@media` (--desktop) { | |
| padding-top: desktop-vw(120); | |
| padding-bottom: desktop-vw(120); | |
| } |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@app/`(marketing)/_sections/explore/explore.module.css around lines 4 - 13,
Replace raw viewport math in the explore.module.css padding rules with the
project's viewport helper functions: use mobile-vw() for the mobile calc(...)
values used in padding-top and padding-bottom and switch to desktop-vw() for the
values inside the `@media` (--desktop) block; keep the existing background and
border-top rule but ensure any remaining numeric expressions that used calc(...
/ ... * 100vw) are converted to the corresponding mobile-vw() or desktop-vw()
helper calls so sizing follows the project's token-driven helpers (reference:
padding-top, padding-bottom, and the `@media` (--desktop) block).
| transition: all 0.3s ease; | ||
| text-decoration: none; | ||
| color: inherit; | ||
| min-height: calc(180 / 375 * 100vw); | ||
|
|
||
| @media (--desktop) { | ||
| padding: calc(32 / 1440 * 100vw); | ||
| min-height: calc(220 / 1440 * 100vw); | ||
| } | ||
|
|
||
| &:hover { | ||
| background: color-mix(in srgb, var(--color-secondary) 8%, transparent); | ||
| border-color: color-mix(in srgb, var(--color-secondary) 20%, transparent); | ||
| transform: translateY(-2px); | ||
| } |
There was a problem hiding this comment.
Avoid transition: all and non-compositor hover animation.
transition: all (Lines 79 and 157) plus hover color/border transitions (Lines 90-91) animates non-compositor properties; this is a perf and standards violation here.
Proposed change
.card {
@@
- transition: all 0.3s ease;
+ transition: transform 0.3s ease, opacity 0.3s ease;
@@
&:hover {
background: color-mix(in srgb, var(--color-secondary) 8%, transparent);
border-color: color-mix(in srgb, var(--color-secondary) 20%, transparent);
transform: translateY(-2px);
}
@@
.cardIcon {
@@
- transition: all 0.3s ease;
+ transition: transform 0.3s ease, opacity 0.3s ease;
}As per coding guidelines, "Animate only transform and opacity (compositor properties)".
Also applies to: 157-157
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@app/`(marketing)/_sections/explore/explore.module.css around lines 79 - 93,
The rule currently uses "transition: all 0.3s ease" and animates background and
border-color in the "&:hover" block (non-compositor properties); change this to
animate only compositor properties: replace "transition: all 0.3s ease" with a
focused transition like "transition: transform 0.3s ease" (and/or "opacity 0.3s
ease"), remove direct background/border-color transitions from the "&:hover"
rule, and implement the color overlay/border visuals via an absolutely
positioned pseudo-element (or separate element) whose opacity is toggled on
hover so only transform/opacity are animated; update the selector containing
"min-height" and the "&:hover" block accordingly.
| &:hover .cardAccent { | ||
| opacity: 0.6; | ||
| } |
There was a problem hiding this comment.
🧹 Nitpick | 🔵 Trivial
Remove or wire up unused .cardAccent styles.
Hover targets .cardAccent (Lines 100-102), but the Explore card markup currently renders no element with that class, so this path is dead.
Also applies to: 195-210
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@app/`(marketing)/_sections/explore/explore.module.css around lines 100 - 102,
The hover rule &:hover .cardAccent targets a non-existent element; either remove
the unused .cardAccent CSS blocks (including the other occurrences flagged) or
add an element with class="cardAccent" into the Explore card component markup so
the hover opacity change has an effect; locate the &:hover .cardAccent selector
in the explore.module.css and either delete those rules or wire up the Explore
card template to include a .cardAccent element (ensuring any related styles like
opacity and positioning are kept consistent).
| type Feature = { | ||
| title: string | ||
| description: string | ||
| href: string | ||
| tags?: string[] | ||
| badge?: string | ||
| featured?: boolean | ||
| } |
There was a problem hiding this comment.
🛠️ Refactor suggestion | 🟠 Major
Use interface for object-shape types in this file.
Lines 10-17 and the inline prop object types on Lines 86 and 113 should be interface-based per repo typing conventions.
Proposed change
-type Feature = {
+interface Feature {
title: string
description: string
href: string
tags?: string[]
badge?: string
featured?: boolean
}
+
+interface FeatureCardProps {
+ feature: Feature
+}
+
+interface CategoryHeaderProps {
+ title: string
+}
-function FeatureCard({ feature }: { feature: Feature }) {
+function FeatureCard({ feature }: FeatureCardProps) {
return (
@@
-function CategoryHeader({ title }: { title: string }) {
+function CategoryHeader({ title }: CategoryHeaderProps) {As per coding guidelines, "Use 'interface' for object shapes and 'type' for unions in TypeScript".
Also applies to: 86-86, 113-113
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@app/`(marketing)/_sections/explore/index.tsx around lines 10 - 17, Change the
declared object-shaped types to interfaces: replace the type alias "type Feature
= { ... }" with "interface Feature { ... }" and likewise convert the two inline
prop object types used at the component prop positions on lines 86 and 113 into
named interfaces. Update any references to those shapes to use the new interface
names so the file follows the repo rule "interface for object shapes, type for
unions."
| * Or keep it as inspiration for your own marketing pages! | ||
| */ | ||
| import { Wrapper } from '@/components/layout/wrapper' | ||
| import { Explore } from './(marketing)/_sections/explore' |
There was a problem hiding this comment.
🛠️ Refactor suggestion | 🟠 Major
Use path alias for the new section import.
Line 11 introduces a relative import; switch it to the @/ alias style used by project rules.
Proposed change
-import { Explore } from './(marketing)/_sections/explore'
+import { Explore } from '@/app/(marketing)/_sections/explore'As per coding guidelines, "Use path aliases and never use '../' relative imports" and "Use path aliases for all imports (@/components/, @/lib/, ...)".
📝 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.
| import { Explore } from './(marketing)/_sections/explore' | |
| import { Explore } from '@/app/(marketing)/_sections/explore' |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@app/page.tsx` at line 11, Replace the relative import of the Explore
component in app/page.tsx (the line importing Explore from
'./(marketing)/_sections/explore') with the project path-alias style; locate the
import statement for Explore and update it to use the '@/...' alias that matches
your repo conventions (e.g., import Explore from '@/...'), ensuring the module
path points to the same (marketing)/_sections/explore module via the alias.
| // Navigation links - customize for your project | ||
| const LINKS = [ | ||
| { href: '/', label: 'home' }, | ||
| { href: '/#explore', label: 'explore' }, |
There was a problem hiding this comment.
🧩 Analysis chain
🌐 Web query:
In Next.js 16, does usePathname() include URL hash fragments (e.g., #explore), or only the pathname?
💡 Result:
In Next.js 16, usePathname() returns only the URL pathname (e.g. "/dashboard"). It does not include the hash/fragment (e.g. #explore), and it also strips query strings (e.g. ?v=2). [1][2]
If you need the hash, you must read it on the client (e.g. window.location.hash) and/or listen for hash changes yourself. [2]
Sources: [1] [2]
Fix active-state detection for hash-based navigation.
Line 10 adds /#explore, but usePathname() excludes hash fragments, so this link will never show as active. Update the active-state logic to check window.location.hash for hash-based navigation items.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@components/layout/header/index.tsx` at line 10, The active-state logic
currently uses usePathname() which strips hash fragments so links like
'/#explore' will never become active; update the header component's active
detection (where you map nav items including href '/#explore') to also check the
hash by comparing href's fragment to window.location.hash for hash-based items
(e.g., detect when href includes '#' or startsWith '/#explore'), and guard
against SSR by checking typeof window !== 'undefined' before reading
window.location.hash; keep usePathname() for non-hash links so both path and
hash cases are handled.
Add sophisticated dark UI generator at /generator with header, sidebar, canvas, control panel, and interactive features
Ensure proper styling by wrapping pages with global layout component.
Update metadata, environment variables, and README for Alias branding
There was a problem hiding this comment.
Actionable comments posted: 13
♻️ Duplicate comments (5)
app/(examples)/alias/page.tsx (3)
794-808:⚠️ Potential issue | 🟠 MajorReplace raw anchor tags with the Link wrapper.
These links violate repo lint/plugin rules and will keep checks red.
♻️ Proposed fix
import { Wrapper } from '@/components/layout/wrapper' import cn from 'clsx' +import { Link } from '@/components/ui/link' import { useState } from 'react' @@ - <a href="#motifs" className={s.navLink}> + <Link href="#motifs" className={s.navLink}> 01 Motifs - </a> - <a href="#cards" className={s.navLink}> + </Link> + <Link href="#cards" className={s.navLink}> 02 Cards - </a> - <a href="#digital" className={s.navLink}> + </Link> + <Link href="#digital" className={s.navLink}> 03 Digital - </a> - <a href="#components" className={s.navLink}> + </Link> + <Link href="#components" className={s.navLink}> 04 Components - </a> - <a href="#rationale" className={s.navLink}> + </Link> + <Link href="#rationale" className={s.navLink}> 05 Rationale - </a> + </Link>As per coding guidelines, "No raw
<a>tags; use Link wrapper component instead".🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@app/`(examples)/alias/page.tsx around lines 794 - 808, This code uses raw <a> tags for internal anchors (e.g., the five anchor elements with className={s.navLink} and href="#motifs", "#cards", "#digital", "#components", "#rationale"); replace each raw anchor with the project's Link wrapper component (e.g., Link) preserving the same href, className, and children text content, and add the Link import if missing so lint rules pass.
126-127: 🛠️ Refactor suggestion | 🟠 MajorLimit hover transitions to compositor-safe properties.
transition-all/transition-colorshere animate paint/layout-affecting properties on an already heavy page.♻️ Proposed fix
- 'absolute inset-0 transition-all duration-700 group-hover:opacity-100 opacity-50' + 'absolute inset-0 opacity-50 transition-opacity duration-700 group-hover:opacity-100' - <BracketBox className="self-start px-4 md:px-8 py-2 md:py-4 text-xs font-mono text-[`#F4F4F5`] bg-[`#020202`] hover:bg-[`#111`] transition-colors border border-[`#1a1a1c`] cursor-pointer"> + <BracketBox className="self-start cursor-pointer border border-[`#1a1a1c`] bg-[`#020202`] px-4 py-2 font-mono text-xs text-[`#F4F4F5`] hover:bg-[`#111`] md:px-8 md:py-4"> - <div className="flex flex-col border border-[`#1a1a1c`] bg-[`#030303`] p-6 gap-6 hover:border-[`#333336`] transition-colors group"> + <div className="group flex flex-col gap-6 border border-[`#1a1a1c`] bg-[`#030303`] p-6 hover:border-[`#333336`]">As per coding guidelines, "Animate only
transformandopacityproperties".Also applies to: 511-512, 552-553
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@app/`(examples)/alias/page.tsx around lines 126 - 127, The Tailwind class strings like 'absolute inset-0 transition-all duration-700 group-hover:opacity-100 opacity-50' animate non-compositor properties; replace 'transition-all' (and any 'transition-colors' or similar) with compositor-safe transitions such as 'transition-opacity' or 'transition-transform' depending on what is animated so only opacity and/or transform are animated; update every occurrence of the offending class string in the JSX (the same pattern at the other occurrences) so the components use only 'transition-opacity'/'transition-transform' and keep existing duration and group-hover classes.
80-826:⚠️ Potential issue | 🟠 MajorThis file is still lint-blocked by unsorted Tailwind utility classes.
Biome
useSortedClassesfailures across this file will keepbun run checkfailing.Based on learnings, run
bun run checkbefore committing to confirm Biome linting/type/tests pass.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@app/`(examples)/alias/page.tsx around lines 80 - 826, The file is failing Biome lint because Tailwind utility classes in className strings are not sorted; run bun run check to reproduce, then fix by applying the project's class sorting rule (useSortedClasses or the configured sorter) to every JSX className occurrence (e.g., in MotifsSection, CardVariantA/B/C, DigitalSection, ComponentItem, Wrapper/nav/main/footer blocks and any BracketBox/BizCard/Crosshair usages) so utilities are in the expected order; re-run bun run check and commit only after the Biome/formatter reports zero useSortedClasses failures.app/(marketing)/_sections/explore/index.tsx (1)
10-17: 🛠️ Refactor suggestion | 🟠 MajorUse
interfacefor object-shape typing in this file.
Featureand the inline prop object types should be interface-based per project typing rules.♻️ Proposed refactor
-type Feature = { +interface Feature { title: string description: string href: string tags?: string[] badge?: string featured?: boolean } + +interface FeatureCardProps { + feature: Feature +} + +interface CategoryHeaderProps { + title: string +} -function FeatureCard({ feature }: { feature: Feature }) { +function FeatureCard({ feature }: FeatureCardProps) { @@ -function CategoryHeader({ title }: { title: string }) { +function CategoryHeader({ title }: CategoryHeaderProps) {As per coding guidelines, "Use
interfacefor object shapes andtypefor unions in TypeScript".Also applies to: 95-95, 122-122
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@app/`(marketing)/_sections/explore/index.tsx around lines 10 - 17, The file uses type aliases for object shapes (e.g., the Feature type and inline prop object types used around the component props at/near the Feature declaration and the other object-typed props referenced at lines ~95 and ~122); replace those object-shape type aliases with interface declarations (rename "type Feature = { ... }" to "interface Feature { ... }" and convert the inline prop object types to interfaces), update any references/usages to the new interface names, and ensure none of the converted interfaces use union-only constructs (keep union types as type aliases if present).components/layout/header/index.tsx (1)
10-10:⚠️ Potential issue | 🟡 MinorHash-based nav item cannot become active with pathname-only matching.
usePathname()excludes fragments, so the item at Line 10 (/#explore) won’t match the active check and never gets the active marker.In Next.js App Router, does usePathname() include URL hash fragments like `#explore`, or only the pathname?🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@components/layout/header/index.tsx` at line 10, The nav item uses href '/#explore' but active detection relies on usePathname() which excludes hash fragments, so the item never becomes active; update the active-check logic (where you use usePathname()) to handle links with hashes: when a nav item's href contains a '#' split into basePath and hash, compare basePath to usePathname() and (on the client) compare window.location.hash to the fragment (or read location.hash inside a useEffect to set state) so '/#explore' is considered active when pathname matches and the hash matches; keep server-safe checks (typeof window !== 'undefined') and reference the nav items array, usePathname(), and the active-check function (or component) to apply this change.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@app/`(examples)/alias/page.tsx:
- Around line 823-825: Replace the hardcoded "© 2025" string in the span element
inside page.tsx with a dynamic year expression so the displayed copyright uses
the current year (e.g., use the current Date().getFullYear() value); locate the
span with className "font-mono text-[10px] text-[`#666666`] tracking-widest
uppercase" in app/(examples)/alias/page.tsx and substitute the static year with
a runtime expression that returns the current year.
In `@app/`(examples)/generator/page.module.css:
- Around line 20-21: Replace the fixed viewport height in page.module.css by
changing the height declaration from 100vh to 100dvh so the layout uses dynamic
viewport height (mobile browser chrome safe); locate the height: 100vh; line in
app/(examples)/generator/page.module.css and update it to height: 100dvh; to
follow the coding guideline.
- Around line 337-340: Rename the keyframes identifier pulseHeight to kebab-case
(e.g., pulse-height) and update any references to it in the stylesheet;
specifically rename the `@keyframes` rule pulseHeight to pulse-height and change
the animation-name/animation shorthand usage inside the .sigBar selector that
currently references pulseHeight so it points to pulse-height instead.
In `@app/`(examples)/generator/page.tsx:
- Line 586: Several <label className={s.tLabel}> elements (e.g., the "Logo
Display" label with class s.tLabel and other similar labels flagged at the
listed lines) are not associated with their inputs; update each label to either
include an htmlFor attribute that matches the corresponding input's id or wrap
the related input element inside the <label> so the accessibility relationship
is explicit. Locate each label using the s.tLabel class or the visible label
text (e.g., "Logo Display") and ensure the matching input receives a unique id
(or is nested) and the label uses htmlFor="that-id" for all occurrences
mentioned in the review.
- Around line 215-217: The SVG elements in page.tsx (e.g., the <svg
className="w-5 h-5" ...> used for icons) are missing accessible text; for purely
decorative icons add aria-hidden="true" and role="presentation" on the <svg>,
and for icons that convey meaning or are interactive ensure the parent
interactive element (e.g., buttons) has an accessible name (aria-label,
aria-labelledby, or visible text) or add a <title> element inside the <svg>
(e.g., <title>Close</title>) to provide a label; update each referenced SVG
instance accordingly (those around the provided snippet and the other ranges
called out) so decorative ones are hidden from assistive tech and
meaningful/interactive ones carry an accessible name.
- Around line 252-262: Several interactive buttons (e.g., the one rendering with
key={idx} that uses className={cn(s.btnNav, activeAsset === idx && 'active')}
and onClick={() => setActiveAsset(idx)}, plus other buttons that toggle state or
open panes) lack an explicit type and will default to type="submit"; update each
non-form-submitting button (those using setActiveAsset, handlers that toggle
panels, change tabs, or call local onClick handlers) to include type="button" to
prevent accidental form submissions, keeping all other attributes the same.
- Around line 1-819: Tailwind class ordering is unsorted across many JSX
className usages in the GeneratorPage component (e.g., className props on
elements within GeneratorPage, buttons in Quick Load, segmented controls,
inputs, and SVG wrappers); run the Biome/Tailwind auto-fixer (bun run check
--apply or bun run check before committing) to re-sort classes, then stage the
modified JSX files (the GeneratorPage component) and commit the formatted
changes so class lists in className props are consistent with the linter.
- Line 6: Remove the now-unnecessary useCallback import and all useCallback
wrappers: delete useCallback from the import list in page.tsx and replace uses
like const generateHash = useCallback(...), const loadTemplate =
useCallback(...), const randomizeData = useCallback(...), and const resetAll =
useCallback(...) with plain function or const arrow definitions (e.g., function
generateHash(...) { ... } or const generateHash = (...) => { ... }); ensure
signatures and internal logic remain unchanged and only the memoization wrapper
and import are removed so references to generateHash, loadTemplate,
randomizeData, and resetAll keep working.
- Line 5: The file imports Link from next/link; replace that import to use the
app's wrapper Link component by importing Link from "@/components/ui/link"
instead so all usages of Link in this module (the imported symbol Link) use the
consistent wrapper—update the import statement to reference
"@/components/ui/link" and leave the rest of the code unchanged.
- Line 646: Replace the use of the global parseFloat in the onChange handler
with the namespaced Number.parseFloat to satisfy the useNumberNamespace rule;
locate the onChange callback where setFontScale(parseFloat(e.target.value)) is
called and change it to call Number.parseFloat(e.target.value) before passing
the result to setFontScale so the same numeric conversion logic is preserved.
- Line 570: Replace the nested ternary used to render the template label (the
expression checking key === 'DEFAULT' ? 'DEFAULT' : key === 'OPS' ? 'OPS_CORE' :
'DEV_TECH') with a simple lookup map to improve readability: define a constant
label map (e.g., TEMPLATE_LABELS) mapping keys ('DEFAULT','OPS','DEV') to their
labels and then render TEMPLATE_LABELS[key] (with a safe fallback if key may be
undefined). This keeps the change localized to the rendering site in page.tsx
and references the existing key variable.
- Around line 196-199: The useEffect currently depends on generateHash which
will re-run on every render if generateHash is no longer memoized; change the
initialization effect to run only once by removing generateHash from the
dependency array and using an empty array (useEffect(() => { generateHash() },
[])), or alternatively inline the hash creation logic into the effect or use a
ref (e.g., initializedRef) to guard a one-time call; update the effect around
generateHash in the component accordingly.
In `@PROD-README.md`:
- Around line 27-39: Update the markdown fences in the directory structure and
deploy examples: add a language specifier to the opening triple backticks for
the file-list fence (use "text" or "bash") and ensure there's a blank line
before the deploy code fence, then mark the deploy fence with "bash"; adjust the
same pattern at the other occurrence noted (lines 66-68). Locate the block
containing the directory tree and the "Manual deploy:" section and modify the
opening fences and spacing accordingly so markdownlint stops complaining.
---
Duplicate comments:
In `@app/`(examples)/alias/page.tsx:
- Around line 794-808: This code uses raw <a> tags for internal anchors (e.g.,
the five anchor elements with className={s.navLink} and href="#motifs",
"#cards", "#digital", "#components", "#rationale"); replace each raw anchor with
the project's Link wrapper component (e.g., Link) preserving the same href,
className, and children text content, and add the Link import if missing so lint
rules pass.
- Around line 126-127: The Tailwind class strings like 'absolute inset-0
transition-all duration-700 group-hover:opacity-100 opacity-50' animate
non-compositor properties; replace 'transition-all' (and any 'transition-colors'
or similar) with compositor-safe transitions such as 'transition-opacity' or
'transition-transform' depending on what is animated so only opacity and/or
transform are animated; update every occurrence of the offending class string in
the JSX (the same pattern at the other occurrences) so the components use only
'transition-opacity'/'transition-transform' and keep existing duration and
group-hover classes.
- Around line 80-826: The file is failing Biome lint because Tailwind utility
classes in className strings are not sorted; run bun run check to reproduce,
then fix by applying the project's class sorting rule (useSortedClasses or the
configured sorter) to every JSX className occurrence (e.g., in MotifsSection,
CardVariantA/B/C, DigitalSection, ComponentItem, Wrapper/nav/main/footer blocks
and any BracketBox/BizCard/Crosshair usages) so utilities are in the expected
order; re-run bun run check and commit only after the Biome/formatter reports
zero useSortedClasses failures.
In `@app/`(marketing)/_sections/explore/index.tsx:
- Around line 10-17: The file uses type aliases for object shapes (e.g., the
Feature type and inline prop object types used around the component props
at/near the Feature declaration and the other object-typed props referenced at
lines ~95 and ~122); replace those object-shape type aliases with interface
declarations (rename "type Feature = { ... }" to "interface Feature { ... }" and
convert the inline prop object types to interfaces), update any
references/usages to the new interface names, and ensure none of the converted
interfaces use union-only constructs (keep union types as type aliases if
present).
In `@components/layout/header/index.tsx`:
- Line 10: The nav item uses href '/#explore' but active detection relies on
usePathname() which excludes hash fragments, so the item never becomes active;
update the active-check logic (where you use usePathname()) to handle links with
hashes: when a nav item's href contains a '#' split into basePath and hash,
compare basePath to usePathname() and (on the client) compare
window.location.hash to the fragment (or read location.hash inside a useEffect
to set state) so '/#explore' is considered active when pathname matches and the
hash matches; keep server-safe checks (typeof window !== 'undefined') and
reference the nav items array, usePathname(), and the active-check function (or
component) to apply this change.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
Run ID: 6803acf1-bcff-4397-9fd0-0e6feb971212
📒 Files selected for processing (8)
.env.examplePROD-README.mdapp/(examples)/alias/page.tsxapp/(examples)/generator/page.module.cssapp/(examples)/generator/page.tsxapp/(marketing)/_sections/explore/index.tsxapp/layout.tsxcomponents/layout/header/index.tsx
| <span className="font-mono text-[10px] text-[#666666] tracking-widest uppercase"> | ||
| © 2025 ALIAS. ALL RIGHTS RESERVED. | ||
| </span> |
There was a problem hiding this comment.
Use a dynamic copyright year.
Hardcoding © 2025 is stale as of April 8, 2026 and will drift again.
♻️ Proposed fix
- <span className="font-mono text-[10px] text-[`#666666`] tracking-widest uppercase">
- © 2025 ALIAS. ALL RIGHTS RESERVED.
- </span>
+ <span className="font-mono text-[10px] uppercase tracking-widest text-[`#666666`]">
+ © {new Date().getFullYear()} ALIAS. ALL RIGHTS RESERVED.
+ </span>📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| <span className="font-mono text-[10px] text-[#666666] tracking-widest uppercase"> | |
| © 2025 ALIAS. ALL RIGHTS RESERVED. | |
| </span> | |
| <span className="font-mono text-[10px] uppercase tracking-widest text-[`#666666`]"> | |
| © {new Date().getFullYear()} ALIAS. ALL RIGHTS RESERVED. | |
| </span> |
🧰 Tools
🪛 Biome (2.4.10)
[error] 823-823: These CSS classes should be sorted.
(lint/nursery/useSortedClasses)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@app/`(examples)/alias/page.tsx around lines 823 - 825, Replace the hardcoded
"© 2025" string in the span element inside page.tsx with a dynamic year
expression so the displayed copyright uses the current year (e.g., use the
current Date().getFullYear() value); locate the span with className "font-mono
text-[10px] text-[`#666666`] tracking-widest uppercase" in
app/(examples)/alias/page.tsx and substitute the static year with a runtime
expression that returns the current year.
| width: 100vw; | ||
| height: 100vh; |
There was a problem hiding this comment.
Use 100dvh instead of 100vh for full viewport height.
As per coding guidelines, use dynamic viewport height (dvh) to handle mobile browser chrome correctly. The 100vh value doesn't account for mobile browser UI elements.
🔧 Proposed fix
width: 100vw;
- height: 100vh;
+ height: 100dvh;📝 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.
| width: 100vw; | |
| height: 100vh; | |
| width: 100vw; | |
| height: 100dvh; |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@app/`(examples)/generator/page.module.css around lines 20 - 21, Replace the
fixed viewport height in page.module.css by changing the height declaration from
100vh to 100dvh so the layout uses dynamic viewport height (mobile browser
chrome safe); locate the height: 100vh; line in
app/(examples)/generator/page.module.css and update it to height: 100dvh; to
follow the coding guideline.
| @keyframes pulseHeight { | ||
| 0%, 100% { transform: scaleY(0.4); } | ||
| 50% { transform: scaleY(1); } | ||
| } |
There was a problem hiding this comment.
Rename keyframe to kebab-case per Stylelint convention.
The static analysis flagged: "Expected keyframe name 'pulseHeight' to be kebab-case (keyframes-name-pattern)". Update the keyframe name and its reference in .sigBar.
🔧 Proposed fix
.sigBar {
- animation: pulseHeight 2s infinite ease-in-out;
+ animation: pulse-height 2s infinite ease-in-out;
transform-origin: bottom;
background: var(--gen-theme-color);
}
/* ... */
-@keyframes pulseHeight {
+@keyframes pulse-height {
0%, 100% { transform: scaleY(0.4); }
50% { transform: scaleY(1); }
}🧰 Tools
🪛 Stylelint (17.6.0)
[error] 337-337: Expected keyframe name "pulseHeight" to be kebab-case (keyframes-name-pattern)
(keyframes-name-pattern)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@app/`(examples)/generator/page.module.css around lines 337 - 340, Rename the
keyframes identifier pulseHeight to kebab-case (e.g., pulse-height) and update
any references to it in the stylesheet; specifically rename the `@keyframes` rule
pulseHeight to pulse-height and change the animation-name/animation shorthand
usage inside the .sigBar selector that currently references pulseHeight so it
points to pulse-height instead.
| 'use client' | ||
|
|
||
| import { Wrapper } from '@/components/layout/wrapper' | ||
| import { cn } from '@/utils/cn' | ||
| import Link from 'next/link' | ||
| import { useCallback, useEffect, useRef, useState } from 'react' | ||
| import s from './page.module.css' | ||
|
|
||
| // Types | ||
| type BorderStyle = 'BRACKET' | 'SOLID' | 'MIN' | ||
| type LogoStyle = 'FULL' | 'MIN' | 'NONE' | ||
| type ClassLevel = 'PUBLIC' | 'OPERATIONAL' | 'CLASSIFIED' | 'RESTRICTED' | ||
|
|
||
| interface FormData { | ||
| sysId: string | ||
| name: string | ||
| title: string | ||
| phone: string | ||
| comms: string | ||
| loc: string | ||
| } | ||
|
|
||
| interface Template { | ||
| name: string | ||
| title: string | ||
| loc: string | ||
| comms: string | ||
| phone: string | ||
| sysId: string | ||
| } | ||
|
|
||
| const TEMPLATES: Record<string, Template> = { | ||
| DEFAULT: { name: 'OPERATIVE_NULL', title: 'Systems Architect', loc: 'Sector 7G', comms: 'AX-9918-OMEGA', phone: '+1.800.NUL.VOID', sysId: '0x99A.88' }, | ||
| OPS: { name: 'GHOST_LEAD', title: 'Tactical Command', loc: 'FOB Alpha', comms: 'CMD-001', phone: 'SEC-LINE-99', sysId: 'OPS.CORE.11' }, | ||
| DEV: { name: 'ROOT_ADMIN', title: 'Infrastructure', loc: 'Server Farm D', comms: 'NET-ADMIN-0', phone: '127.0.0.1', sysId: 'DEV.SYS.00' }, | ||
| } | ||
|
|
||
| const RANDOM_POOL = { | ||
| first: ['JOHN', 'JANE', 'ALEX', 'SARAH', 'MARCUS', 'ELENA', 'DAVID', 'CHLOE'], | ||
| last: ['DOE', 'SMITH', 'MERCER', 'CONNOR', 'WRIGHT', 'FISHER', 'MASON', 'VANCE'], | ||
| title: ['FIELD AGENT', 'DATA ANALYST', 'SYS ADMIN', 'OPERATOR', 'SPECIALIST', 'INFILTRATOR', 'HANDLER'], | ||
| loc: ['SECTOR 7G', 'ZION', 'BASE ALPHA', 'OUTPOST 3', 'LANGLEY', 'SITE 19', 'BLACK KNIGHT'], | ||
| commsPref: ['AX', 'BR', 'CX', 'DX', 'EX', 'FX', 'GX'], | ||
| hex: '0123456789ABCDEF', | ||
| } | ||
|
|
||
| const CLASS_COLORS: Record<ClassLevel, string> = { | ||
| PUBLIC: '#888888', | ||
| OPERATIONAL: '#ffffff', | ||
| CLASSIFIED: '#ffd700', | ||
| RESTRICTED: '#9b59b6', | ||
| } | ||
|
|
||
| const ACCENT_COLORS = ['#ffffff', '#888888', '#4a9eff', '#ff6b4a', '#9b59b6'] | ||
|
|
||
| export default function GeneratorPage() { | ||
| // State | ||
| const [formData, setFormData] = useState<FormData>({ | ||
| sysId: '0x99A.88', | ||
| name: 'OPERATIVE_NULL', | ||
| title: 'SYSTEMS ARCHITECT', | ||
| phone: '+1.800.NUL.VOID', | ||
| comms: 'AX-9918-OMEGA', | ||
| loc: 'SECTOR 7G', | ||
| }) | ||
| const [borderStyle, setBorderStyle] = useState<BorderStyle>('BRACKET') | ||
| const [logoStyle, setLogoStyle] = useState<LogoStyle>('FULL') | ||
| const [classLevel, setClassLevel] = useState<ClassLevel>('CLASSIFIED') | ||
| const [accentColor, setAccentColor] = useState('#ffffff') | ||
| const [fontScale, setFontScale] = useState(1) | ||
| const [showQR, setShowQR] = useState(true) | ||
| const [showHash, setShowHash] = useState(true) | ||
| const [autoTimestamp, setAutoTimestamp] = useState(true) | ||
| const [zoom, setZoom] = useState(1) | ||
| const [hash, setHash] = useState('A9.B8.C7.D6.E5.F4') | ||
| const [headerTime, setHeaderTime] = useState('00:00:00:00') | ||
| const [timestamp, setTimestamp] = useState('T-2026.04.09::00:00') | ||
| const [activeAsset, setActiveAsset] = useState(0) | ||
|
|
||
| const hashRef = useRef<HTMLSpanElement>(null) | ||
|
|
||
| // Update time | ||
| useEffect(() => { | ||
| const interval = setInterval(() => { | ||
| const now = new Date() | ||
| const timeStr = now.toISOString().split('T')[1].replace('Z', '').substring(0, 11) | ||
| setHeaderTime(`T-${timeStr}`) | ||
|
|
||
| if (autoTimestamp) { | ||
| const year = now.getUTCFullYear() | ||
| const mo = String(now.getUTCMonth() + 1).padStart(2, '0') | ||
| const da = String(now.getUTCDate()).padStart(2, '0') | ||
| const hr = String(now.getUTCHours()).padStart(2, '0') | ||
| const mi = String(now.getUTCMinutes()).padStart(2, '0') | ||
| setTimestamp(`T-${year}.${mo}.${da}::${hr}:${mi}`) | ||
| } | ||
| }, 50) | ||
| return () => clearInterval(interval) | ||
| }, [autoTimestamp]) | ||
|
|
||
| // Generate hash with scramble animation | ||
| const generateHash = useCallback(() => { | ||
| const chars = '0123456789ABCDEF' | ||
| const blocks = 6 | ||
| const blockLen = 2 | ||
| const finalHash: string[] = [] | ||
|
|
||
| for (let i = 0; i < blocks; i++) { | ||
| let b = '' | ||
| for (let j = 0; j < blockLen; j++) b += chars[Math.floor(Math.random() * chars.length)] | ||
| finalHash.push(b) | ||
| } | ||
|
|
||
| const targetStr = finalHash.join('.') | ||
| let iterations = 0 | ||
|
|
||
| const interval = setInterval(() => { | ||
| let currentStr = '' | ||
| for (let i = 0; i < targetStr.length; i++) { | ||
| if (targetStr[i] === '.') currentStr += '.' | ||
| else if (i < iterations) currentStr += targetStr[i] | ||
| else currentStr += chars[Math.floor(Math.random() * chars.length)] | ||
| } | ||
| setHash(currentStr) | ||
|
|
||
| if (iterations >= targetStr.length) clearInterval(interval) | ||
| iterations += 0.5 | ||
| }, 30) | ||
| }, []) | ||
|
|
||
| // Load template | ||
| const loadTemplate = useCallback((key: string) => { | ||
| const t = TEMPLATES[key] | ||
| if (t) { | ||
| setFormData({ | ||
| sysId: t.sysId, | ||
| name: t.name, | ||
| title: t.title.toUpperCase(), | ||
| phone: t.phone, | ||
| comms: t.comms, | ||
| loc: t.loc.toUpperCase(), | ||
| }) | ||
| generateHash() | ||
| } | ||
| }, [generateHash]) | ||
|
|
||
| // Randomize data | ||
| const randomizeData = useCallback(() => { | ||
| const rFirst = RANDOM_POOL.first[Math.floor(Math.random() * RANDOM_POOL.first.length)] | ||
| const rLast = RANDOM_POOL.last[Math.floor(Math.random() * RANDOM_POOL.last.length)] | ||
| const rTitle = RANDOM_POOL.title[Math.floor(Math.random() * RANDOM_POOL.title.length)] | ||
| const rLoc = RANDOM_POOL.loc[Math.floor(Math.random() * RANDOM_POOL.loc.length)] | ||
| const rCommPref = RANDOM_POOL.commsPref[Math.floor(Math.random() * RANDOM_POOL.commsPref.length)] | ||
| const rCommNum = Math.floor(Math.random() * 9000) + 1000 | ||
|
|
||
| let rSysId = '0x' | ||
| for (let i = 0; i < 3; i++) rSysId += RANDOM_POOL.hex[Math.floor(Math.random() * 16)] | ||
| rSysId += '.' + Math.floor(Math.random() * 99) | ||
|
|
||
| setFormData({ | ||
| sysId: rSysId, | ||
| name: `${rFirst} ${rLast}`, | ||
| title: rTitle, | ||
| phone: `+1.${Math.floor(Math.random() * 900 + 100)}.${Math.floor(Math.random() * 900 + 100)}.${Math.floor(Math.random() * 9000 + 1000)}`, | ||
| comms: `${rCommPref}-${rCommNum}-XX`, | ||
| loc: rLoc, | ||
| }) | ||
| generateHash() | ||
| }, [generateHash]) | ||
|
|
||
| // Reset all | ||
| const resetAll = useCallback(() => { | ||
| setFormData({ sysId: '', name: '', title: '', phone: '', comms: '', loc: '' }) | ||
| }, []) | ||
|
|
||
| // Update form field | ||
| const updateField = (field: keyof FormData, value: string) => { | ||
| setFormData(prev => ({ ...prev, [field]: value.toUpperCase() })) | ||
| } | ||
|
|
||
| // Zoom controls | ||
| const changeZoom = (delta: number) => { | ||
| setZoom(prev => Math.max(0.4, Math.min(1.5, prev + delta))) | ||
| } | ||
|
|
||
| // Get card style class | ||
| const getCardStyleClass = () => { | ||
| switch (borderStyle) { | ||
| case 'BRACKET': return s.styleBracket | ||
| case 'SOLID': return s.styleSolid | ||
| case 'MIN': return s.styleMin | ||
| default: return s.styleBracket | ||
| } | ||
| } | ||
|
|
||
| // Initial hash generation | ||
| useEffect(() => { | ||
| generateHash() | ||
| }, [generateHash]) | ||
|
|
||
| const classColor = CLASS_COLORS[classLevel] | ||
|
|
||
| return ( | ||
| <Wrapper theme="dark"> | ||
| <div | ||
| className={s.page} | ||
| style={{ | ||
| '--gen-theme-color': accentColor, | ||
| '--gen-class-color': classColor, | ||
| '--gen-card-font-scale': fontScale, | ||
| } as React.CSSProperties} | ||
| > | ||
| {/* Close button */} | ||
| <Link href="/" className={s.closeBtn} title="Back to Home"> | ||
| <svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"> | ||
| <path strokeLinecap="square" strokeLinejoin="miter" strokeWidth={2} d="M6 18L18 6M6 6l12 12" /> | ||
| </svg> | ||
| </Link> | ||
|
|
||
| {/* Header */} | ||
| <header className={s.header}> | ||
| <div className={s.headerGradient} /> | ||
| <div className="flex items-center gap-10"> | ||
| <div className="flex flex-col"> | ||
| <span className={cn(s.tLabel, 'text-[10px]')} style={{ color: accentColor }}>SYS.ID</span> | ||
| <span className={cn(s.tMono, 'text-[14px] font-bold tracking-widest')}>ALIAS_GEN_V2</span> | ||
| </div> | ||
| <div className="h-8 w-[1px] bg-[var(--gen-border)]" /> | ||
| <div className="flex items-center gap-3"> | ||
| <div className={cn('w-2 h-2', s.blink)} style={{ background: accentColor }} /> | ||
| <span className={cn(s.tLabel, 'tracking-[0.2em]')}>Uplink: Secure</span> | ||
| </div> | ||
| </div> | ||
| <div className="flex items-center gap-8"> | ||
| <span className={cn(s.tLabel, 'text-[12px]')} style={{ color: accentColor }}>{headerTime}</span> | ||
| <div className={s.crosshair} style={{ width: 20, height: 20 }} /> | ||
| </div> | ||
| </header> | ||
|
|
||
| {/* Main */} | ||
| <main className={s.main}> | ||
| {/* Left Sidebar - Asset Directory */} | ||
| <aside className={s.sidebarLeft}> | ||
| <div className="p-6 border-b border-[var(--gen-border)] flex justify-between items-center"> | ||
| <span className={cn(s.tLabel, 'font-bold text-[var(--gen-fg)]')}>Asset Directory</span> | ||
| <svg className="w-4 h-4 text-[var(--gen-muted)]" fill="none" stroke="currentColor" viewBox="0 0 24 24"> | ||
| <path strokeLinecap="square" strokeLinejoin="miter" strokeWidth={2} d="M4 6h16M4 12h16M4 18h16" /> | ||
| </svg> | ||
| </div> | ||
| <nav className="flex flex-col flex-1 overflow-y-auto py-2"> | ||
| {['ID MATRIX (FRONT/BACK)', 'DOSSIER COVER', 'COMMS SIGNATURE', 'TACTICAL OVERVIEW', 'CLEARANCE BADGE'].map((item, idx) => ( | ||
| <button | ||
| key={idx} | ||
| className={cn(s.btnNav, activeAsset === idx && 'active')} | ||
| onClick={() => setActiveAsset(idx)} | ||
| > | ||
| <span style={{ color: activeAsset === idx ? accentColor : 'var(--gen-muted)', fontWeight: activeAsset === idx ? 700 : 400 }}> | ||
| {String(idx + 1).padStart(2, '0')} | ||
| </span> | ||
| {item} | ||
| </button> | ||
| ))} | ||
| </nav> | ||
| <div className="p-6 border-t border-[var(--gen-border)] mt-auto bg-[var(--gen-surface)]"> | ||
| <div className="flex justify-between items-center"> | ||
| <span className={s.tLabel}>Status</span> | ||
| <span className={cn(s.tMono, 'text-sm', s.blink)} style={{ color: accentColor }}>ONLINE</span> | ||
| </div> | ||
| </div> | ||
| </aside> | ||
|
|
||
| {/* Canvas Area */} | ||
| <section className={cn(s.canvasArea, s.bgGrid)}> | ||
| {/* Toolbar */} | ||
| <div className={s.canvasToolbar}> | ||
| <div className="flex items-center gap-4"> | ||
| <span className={cn(s.tLabel, 'text-[var(--gen-fg)]')}>Preview Canvas</span> | ||
| <span className={s.tLabel}>// ZOOM: <span>{Math.round(zoom * 100)}%</span></span> | ||
| </div> | ||
| <div className="flex items-center gap-2"> | ||
| <button | ||
| className="p-1.5 hover:bg-[var(--gen-surface)] border border-transparent hover:border-[var(--gen-border)] transition-colors text-[var(--gen-muted)] hover:text-[var(--gen-fg)]" | ||
| onClick={() => changeZoom(-0.1)} | ||
| > | ||
| <svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"> | ||
| <path strokeLinecap="square" strokeLinejoin="miter" strokeWidth={2} d="M20 12H4" /> | ||
| </svg> | ||
| </button> | ||
| <button | ||
| className="p-1.5 hover:bg-[var(--gen-surface)] border border-transparent hover:border-[var(--gen-border)] transition-colors text-[var(--gen-muted)] hover:text-[var(--gen-fg)]" | ||
| onClick={() => changeZoom(0.1)} | ||
| > | ||
| <svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"> | ||
| <path strokeLinecap="square" strokeLinejoin="miter" strokeWidth={2} d="M12 4v16m8-8H4" /> | ||
| </svg> | ||
| </button> | ||
| <button | ||
| className="p-1.5 ml-2 hover:bg-[var(--gen-surface)] border border-transparent hover:border-[var(--gen-border)] transition-colors text-[var(--gen-muted)] hover:text-[var(--gen-fg)]" | ||
| onClick={() => setZoom(1)} | ||
| title="Fit to Screen" | ||
| > | ||
| <svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"> | ||
| <path strokeLinecap="square" strokeLinejoin="miter" strokeWidth={2} d="M4 8V4m0 0h4M4 4l5 5m11-1V4m0 0h-4m4 0l-5 5M4 16v4m0 0h4m-4 0l5-5m11 5l-5-5m5 5v-4m0 4h-4" /> | ||
| </svg> | ||
| </button> | ||
| </div> | ||
| </div> | ||
|
|
||
| {/* Canvas scroll area */} | ||
| <div className={s.canvasScroll}> | ||
| <div className={s.canvasScaler} style={{ transform: `scale(${zoom})` }}> | ||
| {/* Front Card */} | ||
| <div className={s.cardWrapper}> | ||
| <div className={cn(s.cardLabel, s.tLabel)}> | ||
| <div className="w-1 h-1" style={{ background: accentColor }} /> FRONT_FACING | ||
| </div> | ||
| <div className={cn(s.cardDimension, s.tLabel)}>800x400 PX</div> | ||
|
|
||
| <div className={cn(s.cardContainer, getCardStyleClass())}> | ||
| <div className={s.bbInner} /> | ||
| <div className={cn('absolute inset-0 pointer-events-none opacity-30', s.bgGridSm)} /> | ||
| <div className={cn('absolute inset-0 opacity-40 pointer-events-none', s.scanline)} /> | ||
|
|
||
| {/* Left side - Logo */} | ||
| <div className={s.cardFrontLeft}> | ||
| <div className="absolute top-8 left-8 flex flex-col gap-1"> | ||
| <span className={cn(s.tLabel, 'text-[10px]')}>SYS.ID</span> | ||
| <span className={cn(s.tMono, 'text-[12px] font-bold')} style={{ color: accentColor }}> | ||
| {formData.sysId || '[REDACTED]'} | ||
| </span> | ||
| </div> | ||
|
|
||
| <div | ||
| className="absolute top-8 right-8 border px-2 py-1 backdrop-blur z-20" | ||
| style={{ | ||
| borderColor: classColor, | ||
| background: classLevel === 'OPERATIONAL' || classLevel === 'PUBLIC' ? 'rgba(0,0,0,0.5)' : `${classColor}10`, | ||
| }} | ||
| > | ||
| <span className={cn(s.tMono, 'text-[10px] tracking-[0.2em] font-bold uppercase')} style={{ color: classColor }}> | ||
| {classLevel} | ||
| </span> | ||
| </div> | ||
|
|
||
| <div className={s.logoContainer} style={{ display: logoStyle === 'NONE' ? 'none' : undefined }}> | ||
| {logoStyle !== 'NONE' && ( | ||
| <div className={s.logoSvgWrapper} style={{ display: logoStyle === 'NONE' ? 'none' : 'block' }}> | ||
| <svg viewBox="0 0 100 100" className="w-32 h-32 fill-none relative z-10 opacity-90" style={{ stroke: 'var(--gen-fg)', strokeWidth: 1 }}> | ||
| <polygon points="50,5 90,28 90,72 50,95 10,72 10,28" style={{ stroke: accentColor }} /> | ||
| <polygon points="50,35 75,65 25,65" /> | ||
| <line x1="50" y1="5" x2="50" y2="35" /> | ||
| <line x1="90" y1="28" x2="75" y2="65" /> | ||
| <line x1="90" y1="72" x2="75" y2="65" /> | ||
| <line x1="50" y1="95" x2="50" y2="35" /> | ||
| <line x1="50" y1="95" x2="75" y2="65" /> | ||
| <line x1="50" y1="95" x2="25" y2="65" /> | ||
| <line x1="10" y1="72" x2="25" y2="65" /> | ||
| <line x1="10" y1="28" x2="25" y2="65" /> | ||
| <line x1="50" y1="35" x2="90" y2="28" /> | ||
| <line x1="50" y1="35" x2="10" y2="28" /> | ||
| <circle cx="50" cy="50" r="4" fill={accentColor} /> | ||
| </svg> | ||
| <svg viewBox="0 0 100 100" className="absolute inset-0 w-full h-full fill-none pointer-events-none scale-[1.7] opacity-40" style={{ stroke: 'var(--gen-border-strong)', strokeWidth: 0.5 }}> | ||
| <circle cx="50" cy="50" r="35" strokeDasharray="2 4" /> | ||
| <circle cx="50" cy="50" r="45" strokeDasharray="1 6" /> | ||
| <line x1="0" y1="50" x2="100" y2="50" strokeDasharray="4 8" /> | ||
| <line x1="50" y1="0" x2="50" y2="100" strokeDasharray="4 8" /> | ||
| </svg> | ||
| </div> | ||
| )} | ||
| {logoStyle !== 'MIN' && ( | ||
| <h1 className="text-5xl font-sans tracking-[0.5em] ml-[0.5em] font-light">ALIAS</h1> | ||
| )} | ||
| </div> | ||
|
|
||
| <div className="absolute bottom-8 left-8 flex items-center gap-3"> | ||
| <div className={cn('w-2 h-2', s.blink)} style={{ background: accentColor }} /> | ||
| <span className={cn(s.tLabel, 'text-[10px] text-[var(--gen-fg)]')}>AGENTWORKS_V2</span> | ||
| </div> | ||
| </div> | ||
|
|
||
| {/* Right side - Data */} | ||
| <div className={s.cardFrontRight}> | ||
| <div className="flex justify-between items-center border-b-2 border-[var(--gen-border)] pb-4 mb-8"> | ||
| <div className="flex items-center gap-3"> | ||
| <div className="w-1 h-3" style={{ background: accentColor }} /> | ||
| <span className={cn(s.tMono, 'text-[12px] tracking-[0.2em] uppercase font-bold')}>Identity Matrix</span> | ||
| </div> | ||
| <span className={cn(s.tLabel, 'text-[10px]')} style={{ color: classColor }}>{classLevel}</span> | ||
| </div> | ||
|
|
||
| <div className="flex flex-col gap-8 flex-1 justify-center pb-12"> | ||
| <div className="flex flex-col gap-1"> | ||
| <span className={cn(s.tLabel, 'text-[11px]')}>Operative Designator</span> | ||
| <div className="flex items-center gap-3 mt-1"> | ||
| <span className={cn(s.tMono, 'text-lg font-bold')} style={{ color: accentColor }}>></span> | ||
| <span className={cn(s.tMono, 'text-2xl uppercase tracking-wider font-bold')}> | ||
| {formData.name || '[REDACTED]'} | ||
| </span> | ||
| </div> | ||
| </div> | ||
|
|
||
| <div className="flex flex-col gap-1"> | ||
| <span className={cn(s.tLabel, 'text-[11px]')}>Clearance / Role</span> | ||
| <div className="flex items-center gap-3 mt-1"> | ||
| <span className={cn(s.tMono, 'text-sm font-bold')} style={{ color: accentColor }}>></span> | ||
| <span className={cn(s.tMono, 'text-lg uppercase tracking-wider text-[var(--gen-accent)]')}> | ||
| {formData.title || '[REDACTED]'} | ||
| </span> | ||
| </div> | ||
| </div> | ||
|
|
||
| <div className="w-full h-px bg-[var(--gen-border-strong)] opacity-50 mt-4" /> | ||
|
|
||
| <div className="flex justify-between items-end"> | ||
| <div className="flex flex-col gap-1"> | ||
| <span className={cn(s.tLabel, 'text-[9px]')}>Location</span> | ||
| <span className={s.tVal}>{formData.loc || '[REDACTED]'}</span> | ||
| </div> | ||
| <div className="flex flex-col gap-1 items-end"> | ||
| <span className={cn(s.tLabel, 'text-[9px]')}>Comms ID</span> | ||
| <span className={s.tVal}>{formData.comms || '[REDACTED]'}</span> | ||
| </div> | ||
| </div> | ||
| </div> | ||
| </div> | ||
| </div> | ||
| </div> | ||
|
|
||
| {/* Back Card */} | ||
| <div className={s.cardWrapper}> | ||
| <div className={cn(s.cardLabel, s.tLabel)}> | ||
| <div className="w-1 h-1 bg-[var(--gen-border-strong)]" /> BACK_FACING | ||
| </div> | ||
| <div className={cn(s.cardDimension, s.tLabel)}>800x400 PX</div> | ||
|
|
||
| <div className={cn(s.cardContainer, getCardStyleClass(), 'flex flex-col bg-[var(--gen-surface)]')}> | ||
| <div className={s.bbInner} /> | ||
| <div className={cn('absolute inset-0 pointer-events-none opacity-20', s.bgGridSm)} /> | ||
| <div className={cn('absolute inset-0 opacity-30 pointer-events-none', s.scanline)} /> | ||
|
|
||
| {/* Header */} | ||
| <div className={s.cardBackHeader}> | ||
| <div className="flex items-center gap-4"> | ||
| <svg viewBox="0 0 10 10" className="w-4 h-4 fill-none" style={{ stroke: accentColor, strokeWidth: 1 }}> | ||
| <circle cx="5" cy="5" r="4" strokeDasharray="2 2" /> | ||
| <circle cx="5" cy="5" r="1" fill={accentColor} /> | ||
| </svg> | ||
| <span className={cn(s.tMono, 'text-[12px] font-bold tracking-[0.2em]')}>SYSTEM DATA // VERIFICATION</span> | ||
| </div> | ||
| <span | ||
| className={cn(s.tLabel, 'text-[10px] px-2 py-1 border border-[var(--gen-border)] bg-[var(--gen-bg)]')} | ||
| style={{ color: classColor }} | ||
| > | ||
| {classLevel} | ||
| </span> | ||
| </div> | ||
|
|
||
| {/* Content */} | ||
| <div className={s.cardBackContent}> | ||
| {/* QR Section */} | ||
| {showQR && ( | ||
| <div className={s.qrSection}> | ||
| <div className={s.qrCode}> | ||
| <div className={cn(s.qrCorner, s.tl)} /> | ||
| <div className={cn(s.qrCorner, s.tr)} /> | ||
| <div className={cn(s.qrCorner, s.bl)} /> | ||
| <div className={cn(s.qrCorner, s.br)} /> | ||
| <svg viewBox="0 0 100 100" className="w-full h-full fill-[var(--gen-fg)]"> | ||
| <rect x="5" y="5" width="25" height="25" fill="none" stroke="var(--gen-fg)" strokeWidth="4" /> | ||
| <rect x="10" y="10" width="15" height="15" /> | ||
| <rect x="70" y="5" width="25" height="25" fill="none" stroke="var(--gen-fg)" strokeWidth="4" /> | ||
| <rect x="75" y="10" width="15" height="15" /> | ||
| <rect x="5" y="70" width="25" height="25" fill="none" stroke="var(--gen-fg)" strokeWidth="4" /> | ||
| <rect x="10" y="75" width="15" height="15" /> | ||
| <path d="M40,10 h10 v10 h-10 z M55,15 h5 v5 h-5 z M45,25 h15 v5 h-15 z M35,40 h10 v20 h-10 z M50,45 h25 v10 h-25 z M15,40 h10 v15 h-10 z M80,40 h10 v30 h-10 z M40,70 h20 v10 h-20 z M65,75 h15 v15 h-15 z M45,85 h10 v10 h-10 z M35,60 h10 v5 h-10 z" /> | ||
| </svg> | ||
| </div> | ||
| <span className={cn(s.tLabel, 'text-[9px] text-center w-full py-1 font-bold bg-[var(--gen-border)] text-[var(--gen-fg)]')}> | ||
| SCAN TO AUTHENTICATE | ||
| </span> | ||
| </div> | ||
| )} | ||
|
|
||
| {/* Data Grid */} | ||
| <div className={s.dataGrid}> | ||
| <div className={s.dataField}> | ||
| <span className={cn(s.tLabel, 'text-[9px] mb-1')}>Operative Name</span> | ||
| <span className={cn(s.tVal, 'font-bold')}>{formData.name || '[REDACTED]'}</span> | ||
| </div> | ||
| <div className={s.dataField}> | ||
| <span className={cn(s.tLabel, 'text-[9px] mb-1')}>System Identifier</span> | ||
| <span className={s.tVal}>{formData.sysId || '[REDACTED]'}</span> | ||
| </div> | ||
| <div className={s.dataField}> | ||
| <span className={cn(s.tLabel, 'text-[9px] mb-1')}>Designation</span> | ||
| <span className={cn(s.tVal, 'text-[var(--gen-accent)]')}>{formData.title || '[REDACTED]'}</span> | ||
| </div> | ||
| <div className={s.dataField}> | ||
| <span className={cn(s.tLabel, 'text-[9px] mb-1')}>Secure Line</span> | ||
| <span className={s.tVal}>{formData.phone || '[REDACTED]'}</span> | ||
| </div> | ||
| <div className={s.dataField}> | ||
| <span className={cn(s.tLabel, 'text-[9px] mb-1')}>Communications ID</span> | ||
| <span className={s.tVal}>{formData.comms || '[REDACTED]'}</span> | ||
| </div> | ||
| <div className={s.dataField}> | ||
| <span className={cn(s.tLabel, 'text-[9px] mb-1')}>Location Code</span> | ||
| <span className={s.tVal}>{formData.loc || '[REDACTED]'}</span> | ||
| </div> | ||
| </div> | ||
| </div> | ||
|
|
||
| {/* Footer */} | ||
| <div className={s.cardBackFooter}> | ||
| {showHash && ( | ||
| <div className="flex items-center gap-4"> | ||
| <span className={cn(s.tLabel, 'text-[10px]')}>AUTH_HASH:</span> | ||
| <span | ||
| ref={hashRef} | ||
| className={cn(s.tMono, 'text-[13px] font-bold tracking-[0.2em]', s.scramble)} | ||
| style={{ color: accentColor }} | ||
| > | ||
| {hash} | ||
| </span> | ||
| </div> | ||
| )} | ||
| <div className="flex items-center gap-6"> | ||
| <span className={cn(s.tLabel, 'text-[10px]')}>{timestamp}</span> | ||
| <div className="flex gap-[2px] h-4 items-end opacity-80"> | ||
| {[100, 80, 60, 90, 70].map((h, i) => ( | ||
| <div key={i} className={s.sigBar} style={{ width: 3, height: `${h}%` }} /> | ||
| ))} | ||
| </div> | ||
| </div> | ||
| </div> | ||
| </div> | ||
| </div> | ||
| </div> | ||
| </div> | ||
|
|
||
| {/* Canvas status */} | ||
| <div className={cn(s.canvasStatus, s.tLabel)}> | ||
| <span>ENG: ALIAS_CORE_V2</span> | ||
| <span>STATUS: RENDER_ACTIVE</span> | ||
| </div> | ||
| </section> | ||
|
|
||
| {/* Right Sidebar - Control Panel */} | ||
| <aside className={s.sidebarRight}> | ||
| <div className="p-6 border-b border-[var(--gen-border)] flex justify-between items-center bg-[var(--gen-bg)]"> | ||
| <span className={cn(s.tLabel, 'font-bold text-[var(--gen-fg)]')}>Control Parameters</span> | ||
| <div className="flex gap-1"> | ||
| <div className="w-1.5 h-1.5 bg-[var(--gen-muted)]" /> | ||
| <div className="w-1.5 h-1.5 bg-[var(--gen-muted)]" /> | ||
| <div className="w-1.5 h-1.5 bg-[var(--gen-muted)]" /> | ||
| </div> | ||
| </div> | ||
|
|
||
| <div className={s.controlPanel}> | ||
| {/* Quick Load */} | ||
| <div className="p-6 border-b border-[var(--gen-border)]"> | ||
| <div className="flex items-center gap-2 mb-4"> | ||
| <div className="w-1 h-3" style={{ background: accentColor }} /> | ||
| <span className={cn(s.tMono, 'text-[11px] font-bold uppercase')}>Quick Load</span> | ||
| </div> | ||
| <div className="grid grid-cols-3 gap-2"> | ||
| {Object.keys(TEMPLATES).map(key => ( | ||
| <button key={key} className={cn(s.btnAction, '!text-[9px] !p-2')} onClick={() => loadTemplate(key)}> | ||
| {key === 'DEFAULT' ? 'DEFAULT' : key === 'OPS' ? 'OPS_CORE' : 'DEV_TECH'} | ||
| </button> | ||
| ))} | ||
| </div> | ||
| </div> | ||
|
|
||
| {/* Appearance */} | ||
| <div className="p-6 border-b border-[var(--gen-border)] bg-[var(--gen-bg)]"> | ||
| <div className="flex items-center gap-2 mb-6"> | ||
| <div className="w-1 h-3" style={{ background: accentColor }} /> | ||
| <span className={cn(s.tMono, 'text-[11px] font-bold uppercase')}>Appearance</span> | ||
| </div> | ||
|
|
||
| <div className="flex flex-col gap-6"> | ||
| {/* Logo Display */} | ||
| <div className="flex flex-col gap-2"> | ||
| <label className={s.tLabel}>Logo Display</label> | ||
| <div className={s.segmentedControl}> | ||
| {(['FULL', 'MIN', 'NONE'] as LogoStyle[]).map(style => ( | ||
| <button | ||
| key={style} | ||
| className={cn(s.segBtn, logoStyle === style && 'active')} | ||
| onClick={() => setLogoStyle(style)} | ||
| > | ||
| {style === 'MIN' ? 'MINIMAL' : style} | ||
| </button> | ||
| ))} | ||
| </div> | ||
| </div> | ||
|
|
||
| {/* Container Frame */} | ||
| <div className="flex flex-col gap-2"> | ||
| <label className={s.tLabel}>Container Frame</label> | ||
| <div className={s.segmentedControl}> | ||
| {(['BRACKET', 'SOLID', 'MIN'] as BorderStyle[]).map(style => ( | ||
| <button | ||
| key={style} | ||
| className={cn(s.segBtn, borderStyle === style && 'active')} | ||
| onClick={() => setBorderStyle(style)} | ||
| > | ||
| {style === 'MIN' ? 'HAIRLINE' : style} | ||
| </button> | ||
| ))} | ||
| </div> | ||
| </div> | ||
|
|
||
| {/* Accent Override */} | ||
| <div className="flex flex-col gap-2"> | ||
| <div className="flex justify-between items-center"> | ||
| <label className={s.tLabel}>Accent Override</label> | ||
| <span className={s.tLabel}>{accentColor.toUpperCase()}</span> | ||
| </div> | ||
| <div className="flex gap-3 mt-1"> | ||
| {ACCENT_COLORS.map(color => ( | ||
| <button | ||
| key={color} | ||
| className={cn(s.colorSwatch, accentColor === color && 'active')} | ||
| style={{ background: color }} | ||
| onClick={() => setAccentColor(color)} | ||
| /> | ||
| ))} | ||
| </div> | ||
| </div> | ||
|
|
||
| {/* Data Scale */} | ||
| <div className="flex flex-col gap-3"> | ||
| <div className="flex justify-between items-center"> | ||
| <label className={s.tLabel}>Data Scale</label> | ||
| <span className={s.tLabel}>{fontScale.toFixed(2)}x</span> | ||
| </div> | ||
| <input | ||
| type="range" | ||
| min="0.8" | ||
| max="1.2" | ||
| step="0.05" | ||
| value={fontScale} | ||
| onChange={e => setFontScale(parseFloat(e.target.value))} | ||
| className={s.rangeSlider} | ||
| /> | ||
| </div> | ||
|
|
||
| {/* Toggles */} | ||
| <div className="flex flex-col gap-4 mt-2"> | ||
| <label className={s.toggleLabel}> | ||
| <span className={cn(s.tLabel, 'text-[var(--gen-fg)]')}>Render QR Block (Back)</span> | ||
| <input | ||
| type="checkbox" | ||
| className={cn('hidden', s.toggleInput)} | ||
| checked={showQR} | ||
| onChange={e => setShowQR(e.target.checked)} | ||
| /> | ||
| <div className={s.toggleSwitch} /> | ||
| </label> | ||
| <label className={s.toggleLabel}> | ||
| <span className={cn(s.tLabel, 'text-[var(--gen-fg)]')}>Render Auth Hash</span> | ||
| <input | ||
| type="checkbox" | ||
| className={cn('hidden', s.toggleInput)} | ||
| checked={showHash} | ||
| onChange={e => setShowHash(e.target.checked)} | ||
| /> | ||
| <div className={s.toggleSwitch} /> | ||
| </label> | ||
| <label className={s.toggleLabel}> | ||
| <span className={cn(s.tLabel, 'text-[var(--gen-fg)]')}>Auto-Sync Timestamp</span> | ||
| <input | ||
| type="checkbox" | ||
| className={cn('hidden', s.toggleInput)} | ||
| checked={autoTimestamp} | ||
| onChange={e => setAutoTimestamp(e.target.checked)} | ||
| /> | ||
| <div className={s.toggleSwitch} /> | ||
| </label> | ||
| </div> | ||
| </div> | ||
| </div> | ||
|
|
||
| {/* Data Matrix */} | ||
| <div className="p-6"> | ||
| <div className="flex items-center justify-between mb-6"> | ||
| <div className="flex items-center gap-2"> | ||
| <div className="w-1 h-3" style={{ background: accentColor }} /> | ||
| <span className={cn(s.tMono, 'text-[11px] font-bold uppercase')}>Data Matrix</span> | ||
| </div> | ||
| <button | ||
| className={cn(s.tLabel, 'hover:text-[var(--gen-fg)] transition-colors flex items-center gap-1')} | ||
| onClick={randomizeData} | ||
| > | ||
| <svg className="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24"> | ||
| <path strokeLinecap="square" strokeLinejoin="miter" strokeWidth={2} d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15" /> | ||
| </svg> | ||
| RND | ||
| </button> | ||
| </div> | ||
|
|
||
| <div className="flex flex-col gap-5"> | ||
| {/* Classification Level */} | ||
| <div className="flex flex-col gap-2"> | ||
| <label className={cn(s.tLabel, 'flex justify-between')}> | ||
| Classification Level <span style={{ color: classColor }}>{classLevel.substring(0, 3)}</span> | ||
| </label> | ||
| <div className={s.segmentedControl}> | ||
| {(['PUBLIC', 'OPERATIONAL', 'CLASSIFIED', 'RESTRICTED'] as ClassLevel[]).map(level => ( | ||
| <button | ||
| key={level} | ||
| className={cn(s.segBtn, classLevel === level && 'active')} | ||
| onClick={() => setClassLevel(level)} | ||
| > | ||
| {level.substring(0, 3)} | ||
| </button> | ||
| ))} | ||
| </div> | ||
| </div> | ||
|
|
||
| {/* Input fields */} | ||
| <div className="flex flex-col gap-1"> | ||
| <label className={s.tLabel}>System ID</label> | ||
| <input | ||
| type="text" | ||
| className={cn(s.inputField, s.tMono)} | ||
| value={formData.sysId} | ||
| onChange={e => updateField('sysId', e.target.value)} | ||
| /> | ||
| </div> | ||
|
|
||
| <div className="flex flex-col gap-1"> | ||
| <label className={s.tLabel}>Operative Name</label> | ||
| <input | ||
| type="text" | ||
| className={cn(s.inputField, s.tMono)} | ||
| value={formData.name} | ||
| onChange={e => updateField('name', e.target.value)} | ||
| /> | ||
| </div> | ||
|
|
||
| <div className="flex flex-col gap-1"> | ||
| <label className={s.tLabel}>Designation / Role</label> | ||
| <input | ||
| type="text" | ||
| className={cn(s.inputField, s.tMono)} | ||
| value={formData.title} | ||
| onChange={e => updateField('title', e.target.value)} | ||
| /> | ||
| </div> | ||
|
|
||
| <div className="grid grid-cols-2 gap-4"> | ||
| <div className="flex flex-col gap-1"> | ||
| <label className={s.tLabel}>Secure Line</label> | ||
| <input | ||
| type="text" | ||
| className={cn(s.inputField, s.tMono)} | ||
| value={formData.phone} | ||
| onChange={e => updateField('phone', e.target.value)} | ||
| /> | ||
| </div> | ||
| <div className="flex flex-col gap-1"> | ||
| <label className={s.tLabel}>Comms ID</label> | ||
| <input | ||
| type="text" | ||
| className={cn(s.inputField, s.tMono)} | ||
| value={formData.comms} | ||
| onChange={e => updateField('comms', e.target.value)} | ||
| /> | ||
| </div> | ||
| </div> | ||
|
|
||
| <div className="flex flex-col gap-1"> | ||
| <label className={s.tLabel}>Location Code</label> | ||
| <input | ||
| type="text" | ||
| className={cn(s.inputField, s.tMono)} | ||
| value={formData.loc} | ||
| onChange={e => updateField('loc', e.target.value)} | ||
| /> | ||
| </div> | ||
| </div> | ||
| </div> | ||
| </div> | ||
|
|
||
| {/* Footer actions */} | ||
| <div className="p-6 border-t border-[var(--gen-border)] bg-[var(--gen-bg)] flex flex-col gap-3 shrink-0"> | ||
| <div className="flex gap-2"> | ||
| <button className={cn(s.btnAction, 'primary', 'flex-1')} onClick={generateHash}> | ||
| <svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"> | ||
| <path strokeLinecap="square" strokeLinejoin="miter" strokeWidth={2} d="M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z" /> | ||
| </svg> | ||
| GEN HASH | ||
| </button> | ||
| <button className={s.btnAction} onClick={resetAll} title="Reset All Fields"> | ||
| <svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"> | ||
| <path strokeLinecap="square" strokeLinejoin="miter" strokeWidth={2} d="M6 18L18 6M6 6l12 12" /> | ||
| </svg> | ||
| </button> | ||
| </div> | ||
| <div className="flex gap-2"> | ||
| <button className={cn(s.btnAction, 'flex-1')}>EXP PNG</button> | ||
| <button className={cn(s.btnAction, 'flex-1')}>EXP PDF</button> | ||
| <button className={cn(s.btnAction, 'px-4')} title="Copy Raw Code"> | ||
| <svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"> | ||
| <path strokeLinecap="square" strokeLinejoin="miter" strokeWidth={2} d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z" /> | ||
| </svg> | ||
| </button> | ||
| </div> | ||
| </div> | ||
| </aside> | ||
| </main> | ||
| </div> | ||
| </Wrapper> | ||
| ) | ||
| } |
There was a problem hiding this comment.
🛠️ Refactor suggestion | 🟠 Major
Run bun run check to auto-fix Tailwind class sorting.
Biome flagged ~70 instances of unsorted CSS classes. These can be automatically fixed by running the linter. Based on learnings, run bun run check before committing to catch and fix these issues.
#!/bin/bash
# Run Biome to auto-fix class sorting
bun run check --apply🧰 Tools
🪛 Biome (2.4.10)
[error] 215-215: Alternative text title element cannot be empty
(lint/a11y/noSvgWithoutTitle)
[error] 215-215: These CSS classes should be sorted.
(lint/nursery/useSortedClasses)
[error] 226-226: These CSS classes should be sorted.
(lint/nursery/useSortedClasses)
[error] 230-230: These CSS classes should be sorted.
(lint/nursery/useSortedClasses)
[error] 244-244: These CSS classes should be sorted.
(lint/nursery/useSortedClasses)
[error] 246-246: Alternative text title element cannot be empty
(lint/a11y/noSvgWithoutTitle)
[error] 246-246: These CSS classes should be sorted.
(lint/nursery/useSortedClasses)
[error] 250-250: These CSS classes should be sorted.
(lint/nursery/useSortedClasses)
[error] 264-264: These CSS classes should be sorted.
(lint/nursery/useSortedClasses)
[error] 265-265: These CSS classes should be sorted.
(lint/nursery/useSortedClasses)
[error] 278-278: Wrap comments inside children within braces.
(lint/suspicious/noCommentText)
[error] 282-282: These CSS classes should be sorted.
(lint/nursery/useSortedClasses)
[error] 285-285: Alternative text title element cannot be empty
(lint/a11y/noSvgWithoutTitle)
[error] 285-285: These CSS classes should be sorted.
(lint/nursery/useSortedClasses)
[error] 290-290: These CSS classes should be sorted.
(lint/nursery/useSortedClasses)
[error] 293-293: Alternative text title element cannot be empty
(lint/a11y/noSvgWithoutTitle)
[error] 293-293: These CSS classes should be sorted.
(lint/nursery/useSortedClasses)
[error] 298-298: These CSS classes should be sorted.
(lint/nursery/useSortedClasses)
[error] 302-302: Alternative text title element cannot be empty
(lint/a11y/noSvgWithoutTitle)
[error] 302-302: These CSS classes should be sorted.
(lint/nursery/useSortedClasses)
[error] 315-315: These CSS classes should be sorted.
(lint/nursery/useSortedClasses)
[error] 321-321: These CSS classes should be sorted.
(lint/nursery/useSortedClasses)
[error] 322-322: These CSS classes should be sorted.
(lint/nursery/useSortedClasses)
[error] 328-328: These CSS classes should be sorted.
(lint/nursery/useSortedClasses)
[error] 334-334: These CSS classes should be sorted.
(lint/nursery/useSortedClasses)
[error] 340-340: These CSS classes should be sorted.
(lint/nursery/useSortedClasses)
[error] 348-348: Alternative text title element cannot be empty
(lint/a11y/noSvgWithoutTitle)
[error] 348-348: These CSS classes should be sorted.
(lint/nursery/useSortedClasses)
[error] 363-363: Alternative text title element cannot be empty
(lint/a11y/noSvgWithoutTitle)
[error] 363-363: These CSS classes should be sorted.
(lint/nursery/useSortedClasses)
[error] 372-372: These CSS classes should be sorted.
(lint/nursery/useSortedClasses)
[error] 377-377: These CSS classes should be sorted.
(lint/nursery/useSortedClasses)
[error] 384-384: These CSS classes should be sorted.
(lint/nursery/useSortedClasses)
[error] 386-386: These CSS classes should be sorted.
(lint/nursery/useSortedClasses)
[error] 387-387: These CSS classes should be sorted.
(lint/nursery/useSortedClasses)
[error] 392-392: These CSS classes should be sorted.
(lint/nursery/useSortedClasses)
[error] 395-395: These CSS classes should be sorted.
(lint/nursery/useSortedClasses)
[error] 396-396: These CSS classes should be sorted.
(lint/nursery/useSortedClasses)
[error] 397-397: These CSS classes should be sorted.
(lint/nursery/useSortedClasses)
[error] 405-405: These CSS classes should be sorted.
(lint/nursery/useSortedClasses)
[error] 406-406: These CSS classes should be sorted.
(lint/nursery/useSortedClasses)
[error] 407-407: These CSS classes should be sorted.
(lint/nursery/useSortedClasses)
[error] 413-413: These CSS classes should be sorted.
(lint/nursery/useSortedClasses)
[error] 415-415: These CSS classes should be sorted.
(lint/nursery/useSortedClasses)
[error] 420-420: These CSS classes should be sorted.
(lint/nursery/useSortedClasses)
[error] 433-433: These CSS classes should be sorted.
(lint/nursery/useSortedClasses)
[error] 439-439: These CSS classes should be sorted.
(lint/nursery/useSortedClasses)
[error] 440-440: These CSS classes should be sorted.
(lint/nursery/useSortedClasses)
[error] 445-445: Alternative text title element cannot be empty
(lint/a11y/noSvgWithoutTitle)
[error] 445-445: These CSS classes should be sorted.
(lint/nursery/useSortedClasses)
[error] 449-449: These CSS classes should be sorted.
(lint/nursery/useSortedClasses)
[error] 449-449: Wrap comments inside children within braces.
(lint/suspicious/noCommentText)
[error] 452-452: These CSS classes should be sorted.
(lint/nursery/useSortedClasses)
[error] 469-469: Alternative text title element cannot be empty
(lint/a11y/noSvgWithoutTitle)
[error] 469-469: These CSS classes should be sorted.
(lint/nursery/useSortedClasses)
[error] 479-479: These CSS classes should be sorted.
(lint/nursery/useSortedClasses)
[error] 488-488: These CSS classes should be sorted.
(lint/nursery/useSortedClasses)
[error] 492-492: These CSS classes should be sorted.
(lint/nursery/useSortedClasses)
[error] 496-496: These CSS classes should be sorted.
(lint/nursery/useSortedClasses)
[error] 500-500: These CSS classes should be sorted.
(lint/nursery/useSortedClasses)
[error] 504-504: These CSS classes should be sorted.
(lint/nursery/useSortedClasses)
[error] 508-508: These CSS classes should be sorted.
(lint/nursery/useSortedClasses)
[error] 521-521: These CSS classes should be sorted.
(lint/nursery/useSortedClasses)
[error] 530-530: These CSS classes should be sorted.
(lint/nursery/useSortedClasses)
[error] 551-551: These CSS classes should be sorted.
(lint/nursery/useSortedClasses)
[error] 554-554: These CSS classes should be sorted.
(lint/nursery/useSortedClasses)
[error] 555-555: These CSS classes should be sorted.
(lint/nursery/useSortedClasses)
[error] 556-556: These CSS classes should be sorted.
(lint/nursery/useSortedClasses)
[error] 562-562: These CSS classes should be sorted.
(lint/nursery/useSortedClasses)
[error] 563-563: These CSS classes should be sorted.
(lint/nursery/useSortedClasses)
[error] 564-564: These CSS classes should be sorted.
(lint/nursery/useSortedClasses)
[error] 565-565: These CSS classes should be sorted.
(lint/nursery/useSortedClasses)
[error] 570-570: Do not nest ternary expressions.
(lint/style/noNestedTernary)
[error] 577-577: These CSS classes should be sorted.
(lint/nursery/useSortedClasses)
[error] 578-578: These CSS classes should be sorted.
(lint/nursery/useSortedClasses)
[error] 579-579: These CSS classes should be sorted.
(lint/nursery/useSortedClasses)
[error] 580-580: These CSS classes should be sorted.
(lint/nursery/useSortedClasses)
[error] 586-586: A form label must be associated with an input.
(lint/a11y/noLabelWithoutControl)
[error] 602-602: A form label must be associated with an input.
(lint/a11y/noLabelWithoutControl)
[error] 618-618: These CSS classes should be sorted.
(lint/nursery/useSortedClasses)
[error] 619-619: A form label must be associated with an input.
(lint/a11y/noLabelWithoutControl)
[error] 622-622: These CSS classes should be sorted.
(lint/nursery/useSortedClasses)
[error] 636-636: These CSS classes should be sorted.
(lint/nursery/useSortedClasses)
[error] 637-637: A form label must be associated with an input.
(lint/a11y/noLabelWithoutControl)
[error] 652-652: These CSS classes should be sorted.
(lint/nursery/useSortedClasses)
[error] 689-689: These CSS classes should be sorted.
(lint/nursery/useSortedClasses)
[error] 691-691: These CSS classes should be sorted.
(lint/nursery/useSortedClasses)
[error] 692-692: These CSS classes should be sorted.
(lint/nursery/useSortedClasses)
[error] 695-695: These CSS classes should be sorted.
(lint/nursery/useSortedClasses)
[error] 698-698: Alternative text title element cannot be empty
(lint/a11y/noSvgWithoutTitle)
[error] 698-698: These CSS classes should be sorted.
(lint/nursery/useSortedClasses)
[error] 708-710: A form label must be associated with an input.
(lint/a11y/noLabelWithoutControl)
[error] 726-726: A form label must be associated with an input.
(lint/a11y/noLabelWithoutControl)
[error] 736-736: A form label must be associated with an input.
(lint/a11y/noLabelWithoutControl)
[error] 746-746: A form label must be associated with an input.
(lint/a11y/noLabelWithoutControl)
[error] 757-757: A form label must be associated with an input.
(lint/a11y/noLabelWithoutControl)
[error] 766-766: A form label must be associated with an input.
(lint/a11y/noLabelWithoutControl)
[error] 777-777: A form label must be associated with an input.
(lint/a11y/noLabelWithoutControl)
[error] 790-790: These CSS classes should be sorted.
(lint/nursery/useSortedClasses)
[error] 793-793: Alternative text title element cannot be empty
(lint/a11y/noSvgWithoutTitle)
[error] 793-793: These CSS classes should be sorted.
(lint/nursery/useSortedClasses)
[error] 799-799: Alternative text title element cannot be empty
(lint/a11y/noSvgWithoutTitle)
[error] 799-799: These CSS classes should be sorted.
(lint/nursery/useSortedClasses)
[error] 808-808: Alternative text title element cannot be empty
(lint/a11y/noSvgWithoutTitle)
[error] 808-808: These CSS classes should be sorted.
(lint/nursery/useSortedClasses)
[error] 252-256: Provide an explicit type prop for the button element.
(lint/a11y/useButtonType)
[error] 253-253: Avoid using the index of an array as key property in an element.
(lint/suspicious/noArrayIndexKey)
[error] 281-284: Provide an explicit type prop for the button element.
(lint/a11y/useButtonType)
[error] 289-292: Provide an explicit type prop for the button element.
(lint/a11y/useButtonType)
[error] 297-301: Provide an explicit type prop for the button element.
(lint/a11y/useButtonType)
[error] 532-532: Avoid using the index of an array as key property in an element.
(lint/suspicious/noArrayIndexKey)
[error] 569-569: Provide an explicit type prop for the button element.
(lint/a11y/useButtonType)
[error] 589-593: Provide an explicit type prop for the button element.
(lint/a11y/useButtonType)
[error] 605-609: Provide an explicit type prop for the button element.
(lint/a11y/useButtonType)
[error] 624-629: Provide an explicit type prop for the button element.
(lint/a11y/useButtonType)
[error] 646-646: Use Number.parseFloat instead of the equivalent global.
(lint/style/useNumberNamespace)
[error] 694-697: Provide an explicit type prop for the button element.
(lint/a11y/useButtonType)
[error] 713-717: Provide an explicit type prop for the button element.
(lint/a11y/useButtonType)
[error] 792-792: Provide an explicit type prop for the button element.
(lint/a11y/useButtonType)
[error] 798-798: Provide an explicit type prop for the button element.
(lint/a11y/useButtonType)
[error] 805-805: Provide an explicit type prop for the button element.
(lint/a11y/useButtonType)
[error] 806-806: Provide an explicit type prop for the button element.
(lint/a11y/useButtonType)
[error] 807-807: Provide an explicit type prop for the button element.
(lint/a11y/useButtonType)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@app/`(examples)/generator/page.tsx around lines 1 - 819, Tailwind class
ordering is unsorted across many JSX className usages in the GeneratorPage
component (e.g., className props on elements within GeneratorPage, buttons in
Quick Load, segmented controls, inputs, and SVG wrappers); run the
Biome/Tailwind auto-fixer (bun run check --apply or bun run check before
committing) to re-sort classes, then stage the modified JSX files (the
GeneratorPage component) and commit the formatted changes so class lists in
className props are consistent with the linter.
|
|
||
| import { Wrapper } from '@/components/layout/wrapper' | ||
| import { cn } from '@/utils/cn' | ||
| import Link from 'next/link' |
There was a problem hiding this comment.
Use the wrapper Link component instead of next/link.
As per coding guidelines, import Link from @/components/ui/link instead of directly from next/link. The wrapper component ensures consistent behavior and styling across the application.
🔧 Proposed fix
-import Link from 'next/link'
+import { Link } from '@/components/ui/link'📝 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.
| import Link from 'next/link' | |
| import { Link } from '@/components/ui/link' |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@app/`(examples)/generator/page.tsx at line 5, The file imports Link from
next/link; replace that import to use the app's wrapper Link component by
importing Link from "@/components/ui/link" instead so all usages of Link in this
module (the imported symbol Link) use the consistent wrapper—update the import
statement to reference "@/components/ui/link" and leave the rest of the code
unchanged.
| <button | ||
| key={idx} | ||
| className={cn(s.btnNav, activeAsset === idx && 'active')} | ||
| onClick={() => setActiveAsset(idx)} | ||
| > | ||
| <span style={{ color: activeAsset === idx ? accentColor : 'var(--gen-muted)', fontWeight: activeAsset === idx ? 700 : 400 }}> | ||
| {String(idx + 1).padStart(2, '0')} | ||
| </span> | ||
| {item} | ||
| </button> | ||
| ))} |
There was a problem hiding this comment.
Add explicit type="button" to buttons that don't submit forms.
Buttons default to type="submit", which can cause unexpected form submissions. Add type="button" to all interactive buttons that aren't intended to submit a form.
🔧 Example fix
-<button className={cn(s.btnNav, activeAsset === idx && 'active')} onClick={() => setActiveAsset(idx)}>
+<button type="button" className={cn(s.btnNav, activeAsset === idx && 'active')} onClick={() => setActiveAsset(idx)}>Apply this pattern to all button elements in the component.
Also applies to: 281-296, 297-306, 569-572, 589-597, 605-613, 624-630, 694-702, 713-720, 792-812
🧰 Tools
🪛 Biome (2.4.10)
[error] 252-256: Provide an explicit type prop for the button element.
(lint/a11y/useButtonType)
[error] 253-253: Avoid using the index of an array as key property in an element.
(lint/suspicious/noArrayIndexKey)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@app/`(examples)/generator/page.tsx around lines 252 - 262, Several
interactive buttons (e.g., the one rendering with key={idx} that uses
className={cn(s.btnNav, activeAsset === idx && 'active')} and onClick={() =>
setActiveAsset(idx)}, plus other buttons that toggle state or open panes) lack
an explicit type and will default to type="submit"; update each
non-form-submitting button (those using setActiveAsset, handlers that toggle
panels, change tabs, or call local onClick handlers) to include type="button" to
prevent accidental form submissions, keeping all other attributes the same.
| <div className="grid grid-cols-3 gap-2"> | ||
| {Object.keys(TEMPLATES).map(key => ( | ||
| <button key={key} className={cn(s.btnAction, '!text-[9px] !p-2')} onClick={() => loadTemplate(key)}> | ||
| {key === 'DEFAULT' ? 'DEFAULT' : key === 'OPS' ? 'OPS_CORE' : 'DEV_TECH'} |
There was a problem hiding this comment.
🧹 Nitpick | 🔵 Trivial
Avoid nested ternary expressions.
Biome flagged a nested ternary on line 570. Refactor to improve readability.
♻️ Proposed fix
-{key === 'DEFAULT' ? 'DEFAULT' : key === 'OPS' ? 'OPS_CORE' : 'DEV_TECH'}
+{(() => {
+ switch (key) {
+ case 'DEFAULT': return 'DEFAULT'
+ case 'OPS': return 'OPS_CORE'
+ default: return 'DEV_TECH'
+ }
+})()}Alternatively, create a label map:
const TEMPLATE_LABELS: Record<string, string> = {
DEFAULT: 'DEFAULT',
OPS: 'OPS_CORE',
DEV: 'DEV_TECH',
}
// Then use: {TEMPLATE_LABELS[key]}📝 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.
| {key === 'DEFAULT' ? 'DEFAULT' : key === 'OPS' ? 'OPS_CORE' : 'DEV_TECH'} | |
| {(() => { | |
| switch (key) { | |
| case 'DEFAULT': return 'DEFAULT' | |
| case 'OPS': return 'OPS_CORE' | |
| default: return 'DEV_TECH' | |
| } | |
| })()} |
🧰 Tools
🪛 Biome (2.4.10)
[error] 570-570: Do not nest ternary expressions.
(lint/style/noNestedTernary)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@app/`(examples)/generator/page.tsx at line 570, Replace the nested ternary
used to render the template label (the expression checking key === 'DEFAULT' ?
'DEFAULT' : key === 'OPS' ? 'OPS_CORE' : 'DEV_TECH') with a simple lookup map to
improve readability: define a constant label map (e.g., TEMPLATE_LABELS) mapping
keys ('DEFAULT','OPS','DEV') to their labels and then render
TEMPLATE_LABELS[key] (with a safe fallback if key may be undefined). This keeps
the change localized to the rendering site in page.tsx and references the
existing key variable.
| <div className="flex flex-col gap-6"> | ||
| {/* Logo Display */} | ||
| <div className="flex flex-col gap-2"> | ||
| <label className={s.tLabel}>Logo Display</label> |
There was a problem hiding this comment.
Associate labels with their input controls.
Biome flagged multiple <label> elements that aren't properly associated with inputs. Use htmlFor with matching id attributes, or wrap the input inside the label.
🔧 Example fix
<div className="flex flex-col gap-1">
- <label className={s.tLabel}>System ID</label>
+ <label htmlFor="sysId" className={s.tLabel}>System ID</label>
<input
+ id="sysId"
type="text"
className={cn(s.inputField, s.tMono)}
value={formData.sysId}
onChange={e => updateField('sysId', e.target.value)}
/>
</div>Apply this pattern to all form fields.
Also applies to: 602-602, 619-619, 637-637, 708-710, 726-726, 736-736, 746-746, 757-757, 766-766, 777-777
🧰 Tools
🪛 Biome (2.4.10)
[error] 586-586: A form label must be associated with an input.
(lint/a11y/noLabelWithoutControl)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@app/`(examples)/generator/page.tsx at line 586, Several <label
className={s.tLabel}> elements (e.g., the "Logo Display" label with class
s.tLabel and other similar labels flagged at the listed lines) are not
associated with their inputs; update each label to either include an htmlFor
attribute that matches the corresponding input's id or wrap the related input
element inside the <label> so the accessibility relationship is explicit. Locate
each label using the s.tLabel class or the visible label text (e.g., "Logo
Display") and ensure the matching input receives a unique id (or is nested) and
the label uses htmlFor="that-id" for all occurrences mentioned in the review.
| max="1.2" | ||
| step="0.05" | ||
| value={fontScale} | ||
| onChange={e => setFontScale(parseFloat(e.target.value))} |
There was a problem hiding this comment.
🧹 Nitpick | 🔵 Trivial
Use Number.parseFloat instead of the global parseFloat.
Per Biome's useNumberNamespace rule, prefer the namespaced version for consistency.
🔧 Proposed fix
-onChange={e => setFontScale(parseFloat(e.target.value))}
+onChange={e => setFontScale(Number.parseFloat(e.target.value))}📝 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.
| onChange={e => setFontScale(parseFloat(e.target.value))} | |
| onChange={e => setFontScale(Number.parseFloat(e.target.value))} |
🧰 Tools
🪛 Biome (2.4.10)
[error] 646-646: Use Number.parseFloat instead of the equivalent global.
(lint/style/useNumberNamespace)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@app/`(examples)/generator/page.tsx at line 646, Replace the use of the global
parseFloat in the onChange handler with the namespaced Number.parseFloat to
satisfy the useNumberNamespace rule; locate the onChange callback where
setFontScale(parseFloat(e.target.value)) is called and change it to call
Number.parseFloat(e.target.value) before passing the result to setFontScale so
the same numeric conversion logic is preserved.
| ``` | ||
| app/ # Pages and routes | ||
| components/ # UI components | ||
| lib/ | ||
| ai/ # Vercel AI Gateway integration | ||
| hooks/ # Custom hooks + stores | ||
| utils/ # Pure utilities | ||
| styles/ # Design system, Tailwind config | ||
| integrations/ # Third-party services (Sanity, Shopify, HubSpot) | ||
| integrations/ # Third-party services (Sanity, Shopify, Twenty CRM) | ||
| webgl/ # 3D graphics (optional) | ||
| dev/ # Debug tools | ||
| scripts/ # CLI tools | ||
| ``` |
There was a problem hiding this comment.
Fix markdown fences to satisfy linting.
Two markdownlint warnings are valid: missing language on the structure fence and missing blank line before the deploy fence.
Suggested doc fix
-```
+```text
app/ # Pages and routes
components/ # UI components
lib/
ai/ # Vercel AI Gateway integration
@@
dev/ # Debug tools
scripts/ # CLI tools@@
Manual deploy:
+
vercel --prod</details>
Also applies to: 66-68
<details>
<summary>🧰 Tools</summary>
<details>
<summary>🪛 markdownlint-cli2 (0.22.0)</summary>
[warning] 27-27: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
</details>
</details>
<details>
<summary>🤖 Prompt for AI Agents</summary>
Verify each finding against the current code and only fix it if needed.
In @PROD-README.md around lines 27 - 39, Update the markdown fences in the
directory structure and deploy examples: add a language specifier to the opening
triple backticks for the file-list fence (use "text" or "bash") and ensure
there's a blank line before the deploy code fence, then mark the deploy fence
with "bash"; adjust the same pattern at the other occurrence noted (lines
66-68). Locate the block containing the directory tree and the "Manual deploy:"
section and modify the opening fences and spacing accordingly so markdownlint
stops complaining.
</details>
<!-- fingerprinting:phantom:triton:hawk:3be6e00b-5099-41ec-b996-297953afaf0e -->
<!-- This is an auto-generated comment by CodeRabbit -->
Remove redundant Script component and fix rendering issue.
v0 Session
Summary by CodeRabbit
New Features
Chores
Documentation