Skip to content

New mjs#344

Open
mikeallisonJS wants to merge 15 commits into
mainfrom
new-mjs
Open

New mjs#344
mikeallisonJS wants to merge 15 commits into
mainfrom
new-mjs

Conversation

@mikeallisonJS
Copy link
Copy Markdown
Owner

@mikeallisonJS mikeallisonJS commented May 13, 2026

Summary by CodeRabbit

  • New Features

    • Interactive AI agent with real-time streaming chat, tool-calls, client-side rate limiting, and optional speech input/output
    • Desktop shell with focused vs. scroll modes and new windowed UI primitives (agent terminal, browser-like window, KDE-style panel)
  • Content

    • Fully populated resume, many job/experience pages, and a reusable job template
    • Environment template documenting AI API key and model fallback list
  • Style

    • “Midnight command center” dark theme, monospaced typography, animated background and cursor effects

Review Change Stack

- Updated global CSS to introduce a new color palette and enhanced styles for various elements.
- Modified layout to utilize new font imports and improved structure for better responsiveness.
- Revamped the Hero component with animated backgrounds and a more engaging design.
- Enhanced the Services component to display offerings in a more structured format with code-like presentation.
- Updated the Header component for better navigation and visual consistency.
- Adjusted the Portfolio component to include new project details and improved layout.

These changes aim to enhance the overall user experience and visual appeal of the application.
- Introduced the AgentTerminal component in the Hero section for enhanced interactivity.
- Adjusted layout styles in the Hero component for improved visual structure and responsiveness.
- Updated next.config.mjs to include outputFileTracingIncludes for API content.

These changes aim to enrich user engagement and streamline the component structure.
- Introduced a friendly error handling function in the agent API to provide user-friendly messages based on different error scenarios.
- Updated the AgentTerminal component to include an Escape key functionality for aborting ongoing processes.
- Added a ThinkingIndicator component to visually represent the agent's processing state with animated feedback.
- Improved input validation in the agent API to ensure the last message is from the user.

These changes aim to improve user interaction and provide clearer feedback during agent operations.
- Updated the AgentTerminal component to improve layout responsiveness and structure.
- Enhanced the Hero component with a more flexible layout and removed unnecessary elements for a cleaner design.
- Adjusted styles to ensure better visual consistency across components.

These changes aim to enhance user experience and improve the overall aesthetic of the application.
- Implemented a custom hook for speech recognition, allowing users to interact with the terminal via voice commands.
- Added a toggle button for starting and stopping speech recognition, enhancing user experience.
- Updated input placeholder to reflect listening state, improving feedback during voice input.

These changes aim to enrich user interaction and provide a more accessible way to engage with the AgentTerminal.
- Added a new breeze-wallpaper class to global CSS for a visually appealing background.
- Removed the Header component from the layout for a cleaner structure.
- Updated the main page layout to include KdeWindow components for Portfolio, Services, and Contact sections, improving organization and interactivity.
- Refined the Hero component layout and styles for better responsiveness and visual consistency.

These changes aim to improve the overall user experience and aesthetic of the application.
- Modified the className of the KdeWindow component to include max-width and margin properties, enhancing its layout and ensuring better responsiveness across different screen sizes.

These changes aim to improve the overall user experience and visual consistency of the application.
- Replaced KdeWindow with BrowserWindow for the Portfolio section, improving the layout and interactivity.
- Updated ProjectCard styles for a more modern appearance and better user experience.
- Adjusted header spacing in the Portfolio component for improved visual consistency.

These changes aim to enhance the overall user experience and aesthetic of the application.
- Replaced individual section components with a unified DesktopShell component for improved organization and readability.
- Updated the Hero component to include the AgentTerminal, enhancing interactivity and user engagement.
- Refined KdePanel to support dynamic navigation and mode switching, improving user experience.

These changes aim to streamline the layout and enhance the overall functionality of the application.
- Introduced new TypeScript interfaces for speech recognition, enhancing type safety and clarity in the application.
- Updated the next-env.d.ts file to reference the correct types for improved compatibility.
- Wrapped the main page layout in a Suspense component to support lazy loading of components, improving performance and user experience.

These changes aim to enhance the application's functionality and maintainability.
- Updated the contact section layout for improved responsiveness and visual appeal by consolidating styles and structure.
- Enhanced markdown rendering in the AgentTerminal component to support inline links and email addresses, improving user interaction.
- Adjusted type definitions in next-env.d.ts for better compatibility with development types.

These changes aim to improve user experience and maintainability of the application.
@vercel
Copy link
Copy Markdown

vercel Bot commented May 13, 2026

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

Project Deployment Actions Updated (UTC)
captainofbass Ready Ready Preview, Comment May 17, 2026 6:54am
mikeallisonjs Ready Ready Preview, Comment May 17, 2026 6:54am
sltdnb Ready Ready Preview, Comment May 17, 2026 6:54am

Request Review

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 13, 2026

📝 Walkthrough

Walkthrough

Streaming OpenRouter agent, SSE POST /api/agent with rate limits and tool-call loop; corpus of projects/jobs/resume with search; executeTool dispatcher; client AgentTerminal UI with speech I/O and TTS; new DesktopShell, window components, and site-wide midnight dark theme.

Changes

Agent Terminal & Portfolio Website Redesign

Layer / File(s) Summary
Environment, Next config, and content data
apps/mikeallisonjs.com/.env.example, apps/mikeallisonjs.com/next.config.mjs, apps/mikeallisonjs.com/content/jobs/*, apps/mikeallisonjs.com/content/resume.md
Adds OPENROUTER_API_KEY/OPENROUTER_MODEL, enables outputFileTracingIncludes for /api/agent, and adds job/resume content used by the agent corpus.
Type declarations (SpeechRecognition, Next dev types)
apps/mikeallisonjs.com/index.d.ts, apps/mikeallisonjs.com/next-env.d.ts
Adds Web Speech API global typings and updates the Next.js dev routes type import path.
Global CSS theme and root layout
apps/mikeallisonjs.com/src/app/global.css, apps/mikeallisonjs.com/src/app/layout.tsx
Introduces "midnight command center" palette, theme token mappings, animations (cursor/orbs/grid), monospaced fonts, and updates root layout to load Mona_Sans and JetBrains_Mono with dark class.
Project dataset and corpus search
apps/mikeallisonjs.com/src/lib/agent/projects.ts, apps/mikeallisonjs.com/src/lib/agent/corpus.ts
Adds typed projects dataset and corpus module parsing frontmatter, exposing job/project/resume readers and a simple keyword-scoring search across corpus items.
OpenRouter streaming client and error types
apps/mikeallisonjs.com/src/lib/agent/openrouter.ts
Defines streaming chat/tool types, OpenRouterError parsing, and streamOpenRouter async generator with fallback model routing and event parsing.
Tool definitions, executor, and rate limiter
apps/mikeallisonjs.com/src/lib/agent/tools.ts, apps/mikeallisonjs.com/src/lib/agent/rate-limit.ts
Provides toolDefinitions, executeTool dispatcher calling corpus/project helpers, and an in-memory per-key rate limiter (1h window, 30 requests).
Agent API endpoint with SSE and tool loop
apps/mikeallisonjs.com/src/app/api/agent/route.ts
Implements POST /api/agent: API key/body validation, message sanitization/truncation, per-client rate limiting, streaming OpenRouter consumption, tool-call detection/execution, and SSE events for text/tool_call/tool_result/usage/done/error.
AgentTerminal component: streaming UI and speech I/O
apps/mikeallisonjs.com/src/components/agent-terminal.tsx
Client-side terminal UI that posts to /api/agent, parses SSE-style stream, incrementally renders Markdown-like content with cursor, supports Web Speech API input and TTS, and displays expandable tool-call results.
UI containers, DesktopShell, and page composition
apps/mikeallisonjs.com/src/components/*, apps/mikeallisonjs.com/src/app/page.tsx
Adds BrowserWindow, KdeWindow, KdePanel (top nav, mode toggle, clock), DesktopShell (focused vs scroll), refactors Header/Hero/Portfolio, removes Services, and composes the root page with new slots.
sequenceDiagram
  participant User
  participant AgentTerminal
  participant API_Agent as POST_/api/agent
  participant OpenRouter
  participant ToolExecutor as executeTool
  participant Corpus

  User->>AgentTerminal: submit message
  AgentTerminal->>API_Agent: POST { messages }
  API_Agent->>API_Agent: validate, sanitize, rate-limit
  API_Agent->>OpenRouter: streaming POST (system + messages + models)
  OpenRouter-->>API_Agent: stream data: text, tool_calls, usage, finish

  loop tool rounds
    API_Agent->>ToolExecutor: executeTool(toolCall)
    ToolExecutor->>Corpus: list/read/search
    Corpus-->>ToolExecutor: JSON result
    ToolExecutor-->>API_Agent: tool_result
    API_Agent->>OpenRouter: append tool result and continue
    OpenRouter-->>API_Agent: more stream events
  end

  API_Agent-->>AgentTerminal: SSE events (text, tool_call, tool_result, usage, done/error)
  AgentTerminal->>User: render streaming transcript and tool outputs
Loading

🎯 4 (Complex) | ⏱️ ~75 minutes

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title 'New mjs' is vague and generic, using non-descriptive terms that don't convey meaningful information about the changeset's primary purpose. Consider a more descriptive title that captures the main objective, such as 'Add AI agent terminal and portfolio redesign' or 'Implement OpenRouter agent with redesigned layout.'
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch new-mjs

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

❤️ Share

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

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 10

🧹 Nitpick comments (16)
apps/mikeallisonjs.com/src/lib/agent/projects.ts (1)

136-147: ⚡ Quick win

Quick style pass: switch exported helpers to const arrows for repo consistency.

listProjects and getProject are function declarations right now; convert them to const ... = (...) => to match project conventions.
As per coding guidelines, "Use const arrow functions instead of function declarations (e.g., 'const toggle = () =>')".

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/mikeallisonjs.com/src/lib/agent/projects.ts` around lines 136 - 147,
Convert the exported helpers listProjects and getProject from function
declarations to const arrow functions for consistency: replace "export function
listProjects() { ... }" with "export const listProjects = () => { ... }" and
"export function getProject(slug: string): ProjectData | undefined { ... }" with
"export const getProject = (slug: string): ProjectData | undefined => { ... }",
preserving their implementations, return types, exports, and any existing
references in the module.
apps/mikeallisonjs.com/src/lib/agent/corpus.ts (2)

149-152: ⚡ Quick win

Search path does serial file reads and that’s gonna feel slow under load, buddy.

Lines 149–152 await each job read one-by-one. Batch with Promise.all so corpus search latency doesn’t grow linearly with job count.

Patch idea
   const jobs = await listJobs()
-  for (const j of jobs) {
-    const record = await getJob(j.slug)
+  const jobRecords = await Promise.all(jobs.map((j) => getJob(j.slug)))
+  for (const [index, j] of jobs.entries()) {
+    const record = jobRecords[index]
     if (!record) continue
     const haystack = [j.company, j.role, record.body].join('\n').toLowerCase()
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/mikeallisonjs.com/src/lib/agent/corpus.ts` around lines 149 - 152, The
code currently awaits getJob serially over the jobs list (variable jobs from
listJobs), causing linear latency; change the loop to fetch records concurrently
by mapping jobs to getJob promises and awaiting Promise.all (e.g., const records
= await Promise.all(jobs.map(j => getJob(j.slug)))), then filter out
null/undefined records before continuing; update any logic that used the per-job
record variable to iterate over the filtered records array instead.

19-107: ⚡ Quick win

Style consistency check: these TS helpers should be const arrow functions.

parseFrontmatter, readJobFile, and exported helpers are declared with function; repo convention asks for const arrows.
As per coding guidelines, "Use const arrow functions instead of function declarations (e.g., 'const toggle = () =>')".

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/mikeallisonjs.com/src/lib/agent/corpus.ts` around lines 19 - 107,
Convert the top-level function declarations to const arrow functions to match
repo convention: change parseFrontmatter to "const parseFrontmatter = (raw:
string): { data: Record<string, unknown>; body: string } => { ... }", change
readJobFile to "const readJobFile = async (slug: string): Promise<JobRecord |
null> => { ... }", and do the same for the exported helpers (listJobs, getJob,
readResume and searchCorpus) so they become "export const listJobs = async (...)
=> { ... }", etc.; preserve all parameter and return type annotations, async
keywords, and existing logic/returns when converting to arrow functions.
apps/mikeallisonjs.com/src/components/desktop-shell.tsx (1)

14-24: ⚡ Quick win

Use a const arrow declaration for DesktopShell.

Line 14 should be a const arrow component to match repo conventions.

As per coding guidelines, "Use const arrow functions instead of function declarations (e.g., 'const toggle = () =>')".

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/mikeallisonjs.com/src/components/desktop-shell.tsx` around lines 14 -
24, Replace the function declaration DesktopShell with a const arrow component
to match repo conventions: change "export function DesktopShell({ ... }: { ...
}) { ... }" to an exported const arrow like "export const DesktopShell = ({
agent, portfolio, services, contact }: { agent: ReactNode; portfolio: ReactNode;
services: ReactNode; contact: ReactNode }) => { ... }", preserving the same
props names and types and keeping the implementation body intact.
apps/mikeallisonjs.com/src/components/browser-window.tsx (2)

3-15: ⚡ Quick win

Convert BrowserWindow to a const arrow component.

Line 3 is a function declaration; please switch to const BrowserWindow = (...) => {} for consistency.

As per coding guidelines, "Use const arrow functions instead of function declarations (e.g., 'const toggle = () =>')".

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/mikeallisonjs.com/src/components/browser-window.tsx` around lines 3 -
15, The BrowserWindow component is declared with a function declaration; convert
it to a const arrow component by replacing "export function BrowserWindow({...})
{ ... }" with "export const BrowserWindow = ({...}) => { ... }" while preserving
the same props (id, title, url, contentClassName, children) and exported name so
external imports still work; ensure TypeScript typings remain attached to the
parameter destructuring exactly as before.

22-27: ⚡ Quick win

Replace inline style objects with Tailwind utility classes.

Lines 22-27 and 41-44 use inline styles; move these values into Tailwind classes/arbitrary values so styling stays in className land.

As per coding guidelines, "Always use Tailwind classes for styling HTML elements; avoid using CSS or style tags in React/Next.js components".

Also applies to: 41-44

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/mikeallisonjs.com/src/components/browser-window.tsx` around lines 22 -
27, The header/container div in the BrowserWindow component uses inline style
for a vertical gradient and border-bottom; replace those inline style properties
with Tailwind utility classes (use bg-[linear-gradient(...)] or bg-gradient-to-b
with appropriate arbitrary color stops and use border-b/[1px] border-b-[`#2e3338`]
for the bottom border) and remove the style prop from the element; do the same
for the second inline-styled element referenced around the later block (lines
41-44) so all styling lives in className using Tailwind arbitrary values (e.g.,
bg-[linear-gradient(...)] and border-b-[`#2e3338`]) and keep existing utility
classes like font-mono and px-2.5 untouched.
apps/mikeallisonjs.com/src/components/kde-window.tsx (2)

3-11: ⚡ Quick win

Use a const arrow declaration for KdeWindow.

Line 3 should follow the project pattern with const KdeWindow = (...) =>.

As per coding guidelines, "Use const arrow functions instead of function declarations (e.g., 'const toggle = () =>')".

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/mikeallisonjs.com/src/components/kde-window.tsx` around lines 3 - 11,
The KdeWindow component is declared with a function declaration; change it to a
const arrow function per project style by replacing "export function
KdeWindow(...)" with an exported const arrow like "export const KdeWindow =
(...) => { ... }", keeping the same props type annotation ({ id?: string; title:
string; children: ReactNode }) and returning the same JSX; ensure any named
export usage remains valid and update any tooling expectations (e.g.,
displayName) if your project relies on it.

20-23: ⚡ Quick win

Move title-bar inline styles into Tailwind classes.

The inline style on Line 20 should be converted to Tailwind/arbitrary utilities to stay aligned with component styling rules.

As per coding guidelines, "Always use Tailwind classes for styling HTML elements; avoid using CSS or style tags in React/Next.js components".

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/mikeallisonjs.com/src/components/kde-window.tsx` around lines 20 - 23,
The title-bar element in kde-window.tsx uses an inline style object for the
gradient background and bottom border; replace that style prop with Tailwind
classes (either the built-in gradient utilities like bg-gradient-to-b
from-[`#3b4045`] to-[`#31363b`] and border-b border-[`#2e3338`] or an equivalent
arbitrary bg-[linear-gradient(...)] utility) and move the classes onto the same
element (the title bar in the KDEWindow component) via className so no inline
style remains.
apps/mikeallisonjs.com/src/components/kde-panel.tsx (2)

23-45: ⚡ Quick win

Use const arrow components for Clock and KdePanel.

Lines 23 and 35 are function declarations; switch both to const-arrow style for consistency.

As per coding guidelines, "Use const arrow functions instead of function declarations (e.g., 'const toggle = () =>')".

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/mikeallisonjs.com/src/components/kde-panel.tsx` around lines 23 - 45,
Replace the function declarations with const arrow components to follow the
project's style guide: change the top-level "function Clock()" to "const Clock =
() =>" and change "export function KdePanel(...)" to "export const KdePanel =
({...}) =>"; ensure you keep the existing props/type annotations, hooks
(useState/useEffect), return JSX, and the export semantics intact so behavior
doesn't change.

49-49: ⚡ Quick win

Swap inline color styles for Tailwind classes/tokens.

The inline style props here should be expressed in className utilities so the component stays fully Tailwind-driven.

As per coding guidelines, "Always use Tailwind classes for styling HTML elements; avoid using CSS or style tags in React/Next.js components".

Also applies to: 56-57

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/mikeallisonjs.com/src/components/kde-panel.tsx` at line 49, The inline
style on the element in kde-panel.tsx (currently using style={{ background:
'#1b1e20', borderBottom: '1px solid `#2d3136`' }}) should be replaced with
equivalent Tailwind className utilities—use a bg-... token or a custom color
token (e.g., bg-[theme-color] or bg-[`#1b1e20`] if necessary) and a border-b with
the appropriate border color class (e.g., border-b border-[`#2d3136`] or a token
like border-gray-700) on the same element; also update the other inline style
occurrences referenced around the same component (lines ~56-57) to use Tailwind
classes so the KDEPanel component (kde-panel.tsx) is fully Tailwind-driven.
apps/mikeallisonjs.com/src/app/page.tsx (1)

12-12: ⚡ Quick win

Convert Page and ContactSection to const arrow functions.

Lines 12 and 47 should use const-arrow declarations to stay in lockstep with the project pattern, champ.

As per coding guidelines, "Use const arrow functions instead of function declarations (e.g., 'const toggle = () =>')".

Also applies to: 47-47

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/mikeallisonjs.com/src/app/page.tsx` at line 12, Replace the function
declarations with const arrow functions: change "export default function Page()"
to "const Page = () => { ... }" and then add "export default Page" (since
"export default const" is invalid), and change "function ContactSection()" to
"const ContactSection = () => { ... }"; preserve all internal
logic/props/returns and update any local references if needed so the module
still exports Page as default and uses ContactSection internally.
apps/mikeallisonjs.com/src/app/layout.tsx (1)

29-33: ⚡ Quick win

Use a const arrow for RootLayout declaration.

Line 29 uses a function declaration; swap to a const RootLayout = (...) => to match project style, my dude.

As per coding guidelines, "Use const arrow functions instead of function declarations (e.g., 'const toggle = () =>')".

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/mikeallisonjs.com/src/app/layout.tsx` around lines 29 - 33, Replace the
function declaration for RootLayout with a const arrow function to match project
style: change the declaration of RootLayout (currently "export default function
RootLayout({ children }: { children: React.ReactNode })") to a const arrow
assigned to the same exported identifier (e.g., "const RootLayout = ({ children
}: { children: React.ReactNode }) => { ... }" and keep the existing export
default), ensuring the props type and returned JSX remain unchanged.
apps/mikeallisonjs.com/src/lib/agent/openrouter.ts (2)

60-76: 💤 Low value

O-o-okay, Mister Helper Function Man — let's make this an arrow, alright?

Per the repo's coding guidelines, this should be a const arrow. T-t-totally easy fix, no biggie.

♻️ Proposed tweak
-function parseOpenRouterErrorBody(body: string): {
-  code?: number | string
-  message?: string
-} {
+const parseOpenRouterErrorBody = (
+  body: string
+): { code?: number | string; message?: string } => {
   if (!body) return {}
   ...
   return { message: body.slice(0, 300) }
 }

As per coding guidelines: "Use const arrow functions instead of function declarations (e.g., 'const toggle = () =>')".

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/mikeallisonjs.com/src/lib/agent/openrouter.ts` around lines 60 - 76, The
function parseOpenRouterErrorBody is a function declaration but must follow the
coding guideline to be a const arrow; replace the declaration with a const arrow
assigned to the same name (const parseOpenRouterErrorBody = (body: string): {
code?: number | string; message?: string } => { ... }) keeping the exact logic
and return types intact, preserve the JSON.parse try/catch and final return, and
ensure exports/usage still reference parseOpenRouterErrorBody unchanged.

127-240: ⚡ Quick win

T-T-Tommy boy, the reader's gettin' a little stuck in there — wrap it in try/finally, would ya?

The reader grabbed on line 127 never gets releaseLock()'d (or cancel()'d) on the early return at line 153, the throw at line 184, or any abort during reader.read(). The response body keeps that lock until garbage collection clears it, which is sloppy hygiene on a hot streaming path. Stick a try/finally around the read loop so the lock is always let go.

🧹 Suggested cleanup
   const reader = res.body.getReader()
   const decoder = new TextDecoder()
   let buffer = ''
 
   const toolAcc = new Map<number, ToolCallAccumulator>()
   let textOut = ''
   let finishReason: string | null = null
   let usage: Usage | null = null
 
-  while (true) {
-    const { done, value } = await reader.read()
-    ...
-  }
+  try {
+    while (true) {
+      const { done, value } = await reader.read()
+      if (done) break
+      buffer += decoder.decode(value, { stream: true })
+      ...
+    }
+  } finally {
+    reader.releaseLock()
+  }

(You'll also want the [DONE] early-return and the throw new OpenRouterError(...) mid-stream to flow through that finally.)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/mikeallisonjs.com/src/lib/agent/openrouter.ts` around lines 127 - 240,
The stream reader obtained via res.body.getReader() in the async function (the
reader/read loop starting at the const reader = res.body.getReader() and the
while(true) reader.read() loop) must be wrapped in a try/finally so the
reader.releaseLock() (or reader.cancel()) is always called on all exits (the
early [DONE] return, the mid-stream throw where OpenRouterError is thrown, and
any aborts during reader.read()); refactor by moving the read-loop into a try
block and call reader.releaseLock() (or cancel) in the finally block so the lock
is always released regardless of returns or exceptions.
apps/mikeallisonjs.com/src/lib/agent/rate-limit.ts (1)

1-28: ⚡ Quick win

Whoa, whoa, whoa — this bucket Map's gonna get real fat and it ain't even sharin' with the other servers.

Two friendly heads-ups on this little rate-limiter, T-Bone:

  1. Per-instance only. On Vercel/Next.js serverless (or any horizontally scaled deploy), each function instance keeps its own buckets Map. A determined visitor can blow way past 30/hour just by hitting different cold starts. If this needs to be real protection (and not just a polite speed bump for the happy path), back it with Redis/Upstash/KV.
  2. Buckets never get evicted. Expired entries hang around in the Map forever — fine for a single short-lived instance, but a slow leak on a long-running one. Easy fix: drop expired keys on access (or sweep them).
🧹 Easy eviction-on-access tweak
   const now = Date.now()
   const existing = buckets.get(key)
   if (!existing || existing.resetAt < now) {
+    if (existing) buckets.delete(key)
     const fresh: Bucket = { count: 1, resetAt: now + WINDOW_MS }
     buckets.set(key, fresh)

(That alone doesn't bound growth across many unique keys — a periodic sweep would, but in serverless the instance churn usually wipes it for you.)

If single-instance is intentional and the 30/hour is just a soft guardrail, totally fine — maybe leave a comment to that effect for the next person to wander in.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/mikeallisonjs.com/src/lib/agent/rate-limit.ts` around lines 1 - 28, The
current in-memory buckets Map used by checkRateLimit (symbols: buckets,
checkRateLimit, WINDOW_MS, LIMIT) is per-instance and never evicts expired
entries; fix it by (1) making checkRateLimit remove an entry when
existing.resetAt < now (delete from buckets before creating a fresh bucket) so
expired keys are not retained, and (2) if you need cross-instance enforcement
replace or back this Map with a shared store (Redis/Upstash/KV) rather than
per-instance state; if per-instance soft-guard behavior is intentional, add a
code comment near buckets explaining that limitation and why it's acceptable.
apps/mikeallisonjs.com/src/components/agent-terminal.tsx (1)

454-459: ⚡ Quick win

Let’s ditch the inline style here and keep it Tailwind-only.

This title bar styling is static, so it can live in classes instead of a style prop.

Proposed refactor
-      <div
-        className="relative flex h-[30px] shrink-0 items-center px-2.5 font-mono text-[11px]"
-        style={{
-          background: 'linear-gradient(to bottom, `#3b4045` 0%, `#31363b` 100%)',
-          borderBottom: '1px solid `#2e3338`',
-        }}
-      >
+      <div className="relative flex h-[30px] shrink-0 items-center border-b border-[`#2e3338`] bg-[linear-gradient(to_bottom,`#3b4045_0`%,`#31363b_100`%)] px-2.5 font-mono text-[11px]">

As per coding guidelines, "Always use Tailwind classes for styling HTML elements; avoid using CSS or style tags in React/Next.js components".

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/mikeallisonjs.com/src/components/agent-terminal.tsx` around lines 454 -
459, The title bar div in the AgentTerminal component uses an inline style prop
for the background gradient and border-bottom; remove the style prop and add
equivalent Tailwind classes to the existing className (keep "relative flex
h-[30px] shrink-0 items-center px-2.5 font-mono text-[11px]"). Specifically, add
bg-gradient-to-b from-[`#3b4045`] to-[`#31363b`] and border-b border-[`#2e3338`] to
the className so the gradient and border are expressed with Tailwind instead of
inline style.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@apps/mikeallisonjs.com/content/jobs/agilix.md`:
- Line 13: The job entry contains placeholder text (e.g., the string
"Placeholder — replace with the real story." and other occurrences of the word
"replace") in the Agilix job markdown; replace these placeholder phrases with
the finalized job description and accurate work-history content so the agent
won't return filler responses, ensuring all instances of "replace" in that
Agilix entry are updated to the final copy.

In `@apps/mikeallisonjs.com/content/jobs/jesusfilm.md`:
- Line 13: Replace the literal placeholder text "Placeholder — replace with the
real story. Keep the wins concrete." in the jesusfilm content with a finalized
narrative paragraph that describes the real story and concrete wins (no
placeholder wording), remove the placeholder line entirely, and commit the
updated markdown so the knowledge base/build no longer contains the placeholder
string.

In `@apps/mikeallisonjs.com/content/resume.md`:
- Around line 14-16: The resume.md still contains placeholder directives at the
spots indicated in the diff (the "Replace the contents below..." block around
lines 14–16 and the placeholder at line 35); open content/resume.md, remove
those draft instruction lines and replace them with the final resume copy (full
bio, skills, employment history, education, and contact details) so that the
read_resume tool returns only user-facing resume text; ensure no remaining
developer/instructional comments remain in the file and save/commit the updated
content.

In `@apps/mikeallisonjs.com/src/app/api/agent/route.ts`:
- Around line 22-25: The current MODELS computation uses the nullish coalescing
operator so an empty OPENROUTER_MODEL string yields an empty list and later
errors; change the logic that builds MODELS (the constant named MODELS that
references process.env.OPENROUTER_MODEL and DEFAULT_MODELS) to treat an
empty/whitespace OPENROUTER_MODEL as "not set" and fall back to DEFAULT_MODELS
(e.g. trim and check for truthiness before splitting), so streamOpenRouter
always sees at least the default model(s).
- Around line 153-263: The stream created in the ReadableStream start handler
doesn't propagate client disconnects so streamOpenRouter keeps running; fix by
creating an AbortController, forward req.signal into it (or abort
controller.signal into req.signal by listening to req.signal), add a
controller.cancel = () => abortController.abort() on the ReadableStream, and
pass abortController.signal into the streamOpenRouter(...) call (the generator
variable) so the generator can halt when the client disconnects; ensure any
awaits inside the loop (e.g., generator.next() and executeTool calls) will be
cancelled when the signal aborts.

In `@apps/mikeallisonjs.com/src/components/agent-terminal.tsx`:
- Around line 239-244: The effect that always sets el.scrollTop =
el.scrollHeight on turns updates is forcing scroll-to-bottom; change it so the
auto-scroll only happens when the user is already at (or very near) the bottom.
Use the existing scrollRef, isAtBottom and setIsAtBottom state (or derive a new
isAtBottom boolean) and add a scroll listener on the container to update
isAtBottom based on el.scrollHeight - el.scrollTop - el.clientHeight <=
threshold; in the useEffect that currently watches turns, only set el.scrollTop
= el.scrollHeight and setIsAtBottom(true) when isAtBottom is true (otherwise
leave the scroll position alone).
- Around line 716-734: The toggle button should expose its expanded/collapsed
state and be linked to the details panel: add aria-expanded={open},
aria-controls pointing to a stable id (e.g. `call-result-${call.id}`), and an
accessible aria-label (e.g. `Toggle ${call.name} details`) on the button
element, add an onKeyDown handler that toggles via Enter/Space (calling setOpen)
to complement onClick, and give the result pane that same id plus role="region"
and aria-hidden={!open} so screen readers know when it is visible; reference the
existing setOpen, open, call.result, call.name, summarizeArgs and ensure the
generated id uses a unique call identifier (call.id or similar).

In `@apps/mikeallisonjs.com/src/components/browser-window.tsx`:
- Around line 45-93: The decorative browser control buttons (elements with
aria-labels "Back", "Forward", "Reload", "More" in the browser-window.tsx
markup) are currently interactive but have no behavior; either make them truly
interactive by wiring click and keyboard handlers or make them non-interactive
decorative elements. Fix option A: implement onClick handlers (e.g., handleBack,
handleForward, handleReload, handleMore) and corresponding onKeyDown to support
Enter/Space, keep tabIndex={0}, and ensure accessible labels remain. Fix option
B: if they are purely decorative, replace the <button> tags with non-interactive
elements (e.g., <span> or <div>) or add aria-hidden="true" and remove tabIndex
to prevent keyboard focus. Update the elements referenced by their aria-labels
and the URL span usage accordingly so the component behavior and accessibility
match intent.

In `@apps/mikeallisonjs.com/src/components/portfolio.tsx`:
- Around line 72-93: The overlay controls are only revealed via group-hover
which breaks keyboard/touch access; update the overlay div (the element using
class "group-hover:opacity-100") to be visible by default on small screens and
only hide/show by hover/focus on larger screens (e.g. use responsive Tailwind
like default opacity-100 and sm:opacity-0 sm:group-hover:opacity-100
sm:focus-within:opacity-100), and add accessibility attributes/handlers to the
action links (the Link elements rendering visit and source): include descriptive
aria-labels (e.g. aria-label={`Visit ${project.name}`} and aria-label={`View
source for ${project.name}`}), ensure each link is keyboard-focusable (anchors
are by default, but add tabindex if you changed element types), and attach
onClick and onKeyDown handlers that perform the same navigation behavior for
activation via keyboard. Reference the overlay div class
(group-hover:opacity-100) and the Link instances that use project.websiteUrl and
project.githubUrl to locate changes.

In `@apps/mikeallisonjs.com/src/lib/agent/corpus.ts`:
- Around line 93-95: getJob may miss jobs on case-sensitive filesystems because
it validates with a case-insensitive regex but then passes the original slug to
readJobFile; normalize the slug first by calling trim().toLowerCase() and assign
it to a local variable, validate that normalizedSlug against /^[a-z0-9-]+$/ and
then call readJobFile(normalizedSlug) so both validation and file access use the
same canonical lowercased value (refer to getJob and readJobFile).

---

Nitpick comments:
In `@apps/mikeallisonjs.com/src/app/layout.tsx`:
- Around line 29-33: Replace the function declaration for RootLayout with a
const arrow function to match project style: change the declaration of
RootLayout (currently "export default function RootLayout({ children }: {
children: React.ReactNode })") to a const arrow assigned to the same exported
identifier (e.g., "const RootLayout = ({ children }: { children: React.ReactNode
}) => { ... }" and keep the existing export default), ensuring the props type
and returned JSX remain unchanged.

In `@apps/mikeallisonjs.com/src/app/page.tsx`:
- Line 12: Replace the function declarations with const arrow functions: change
"export default function Page()" to "const Page = () => { ... }" and then add
"export default Page" (since "export default const" is invalid), and change
"function ContactSection()" to "const ContactSection = () => { ... }"; preserve
all internal logic/props/returns and update any local references if needed so
the module still exports Page as default and uses ContactSection internally.

In `@apps/mikeallisonjs.com/src/components/agent-terminal.tsx`:
- Around line 454-459: The title bar div in the AgentTerminal component uses an
inline style prop for the background gradient and border-bottom; remove the
style prop and add equivalent Tailwind classes to the existing className (keep
"relative flex h-[30px] shrink-0 items-center px-2.5 font-mono text-[11px]").
Specifically, add bg-gradient-to-b from-[`#3b4045`] to-[`#31363b`] and border-b
border-[`#2e3338`] to the className so the gradient and border are expressed with
Tailwind instead of inline style.

In `@apps/mikeallisonjs.com/src/components/browser-window.tsx`:
- Around line 3-15: The BrowserWindow component is declared with a function
declaration; convert it to a const arrow component by replacing "export function
BrowserWindow({...}) { ... }" with "export const BrowserWindow = ({...}) => {
... }" while preserving the same props (id, title, url, contentClassName,
children) and exported name so external imports still work; ensure TypeScript
typings remain attached to the parameter destructuring exactly as before.
- Around line 22-27: The header/container div in the BrowserWindow component
uses inline style for a vertical gradient and border-bottom; replace those
inline style properties with Tailwind utility classes (use
bg-[linear-gradient(...)] or bg-gradient-to-b with appropriate arbitrary color
stops and use border-b/[1px] border-b-[`#2e3338`] for the bottom border) and
remove the style prop from the element; do the same for the second inline-styled
element referenced around the later block (lines 41-44) so all styling lives in
className using Tailwind arbitrary values (e.g., bg-[linear-gradient(...)] and
border-b-[`#2e3338`]) and keep existing utility classes like font-mono and px-2.5
untouched.

In `@apps/mikeallisonjs.com/src/components/desktop-shell.tsx`:
- Around line 14-24: Replace the function declaration DesktopShell with a const
arrow component to match repo conventions: change "export function
DesktopShell({ ... }: { ... }) { ... }" to an exported const arrow like "export
const DesktopShell = ({ agent, portfolio, services, contact }: { agent:
ReactNode; portfolio: ReactNode; services: ReactNode; contact: ReactNode }) => {
... }", preserving the same props names and types and keeping the implementation
body intact.

In `@apps/mikeallisonjs.com/src/components/kde-panel.tsx`:
- Around line 23-45: Replace the function declarations with const arrow
components to follow the project's style guide: change the top-level "function
Clock()" to "const Clock = () =>" and change "export function KdePanel(...)" to
"export const KdePanel = ({...}) =>"; ensure you keep the existing props/type
annotations, hooks (useState/useEffect), return JSX, and the export semantics
intact so behavior doesn't change.
- Line 49: The inline style on the element in kde-panel.tsx (currently using
style={{ background: '#1b1e20', borderBottom: '1px solid `#2d3136`' }}) should be
replaced with equivalent Tailwind className utilities—use a bg-... token or a
custom color token (e.g., bg-[theme-color] or bg-[`#1b1e20`] if necessary) and a
border-b with the appropriate border color class (e.g., border-b
border-[`#2d3136`] or a token like border-gray-700) on the same element; also
update the other inline style occurrences referenced around the same component
(lines ~56-57) to use Tailwind classes so the KDEPanel component (kde-panel.tsx)
is fully Tailwind-driven.

In `@apps/mikeallisonjs.com/src/components/kde-window.tsx`:
- Around line 3-11: The KdeWindow component is declared with a function
declaration; change it to a const arrow function per project style by replacing
"export function KdeWindow(...)" with an exported const arrow like "export const
KdeWindow = (...) => { ... }", keeping the same props type annotation ({ id?:
string; title: string; children: ReactNode }) and returning the same JSX; ensure
any named export usage remains valid and update any tooling expectations (e.g.,
displayName) if your project relies on it.
- Around line 20-23: The title-bar element in kde-window.tsx uses an inline
style object for the gradient background and bottom border; replace that style
prop with Tailwind classes (either the built-in gradient utilities like
bg-gradient-to-b from-[`#3b4045`] to-[`#31363b`] and border-b border-[`#2e3338`] or an
equivalent arbitrary bg-[linear-gradient(...)] utility) and move the classes
onto the same element (the title bar in the KDEWindow component) via className
so no inline style remains.

In `@apps/mikeallisonjs.com/src/lib/agent/corpus.ts`:
- Around line 149-152: The code currently awaits getJob serially over the jobs
list (variable jobs from listJobs), causing linear latency; change the loop to
fetch records concurrently by mapping jobs to getJob promises and awaiting
Promise.all (e.g., const records = await Promise.all(jobs.map(j =>
getJob(j.slug)))), then filter out null/undefined records before continuing;
update any logic that used the per-job record variable to iterate over the
filtered records array instead.
- Around line 19-107: Convert the top-level function declarations to const arrow
functions to match repo convention: change parseFrontmatter to "const
parseFrontmatter = (raw: string): { data: Record<string, unknown>; body: string
} => { ... }", change readJobFile to "const readJobFile = async (slug: string):
Promise<JobRecord | null> => { ... }", and do the same for the exported helpers
(listJobs, getJob, readResume and searchCorpus) so they become "export const
listJobs = async (...) => { ... }", etc.; preserve all parameter and return type
annotations, async keywords, and existing logic/returns when converting to arrow
functions.

In `@apps/mikeallisonjs.com/src/lib/agent/openrouter.ts`:
- Around line 60-76: The function parseOpenRouterErrorBody is a function
declaration but must follow the coding guideline to be a const arrow; replace
the declaration with a const arrow assigned to the same name (const
parseOpenRouterErrorBody = (body: string): { code?: number | string; message?:
string } => { ... }) keeping the exact logic and return types intact, preserve
the JSON.parse try/catch and final return, and ensure exports/usage still
reference parseOpenRouterErrorBody unchanged.
- Around line 127-240: The stream reader obtained via res.body.getReader() in
the async function (the reader/read loop starting at the const reader =
res.body.getReader() and the while(true) reader.read() loop) must be wrapped in
a try/finally so the reader.releaseLock() (or reader.cancel()) is always called
on all exits (the early [DONE] return, the mid-stream throw where
OpenRouterError is thrown, and any aborts during reader.read()); refactor by
moving the read-loop into a try block and call reader.releaseLock() (or cancel)
in the finally block so the lock is always released regardless of returns or
exceptions.

In `@apps/mikeallisonjs.com/src/lib/agent/projects.ts`:
- Around line 136-147: Convert the exported helpers listProjects and getProject
from function declarations to const arrow functions for consistency: replace
"export function listProjects() { ... }" with "export const listProjects = () =>
{ ... }" and "export function getProject(slug: string): ProjectData | undefined
{ ... }" with "export const getProject = (slug: string): ProjectData | undefined
=> { ... }", preserving their implementations, return types, exports, and any
existing references in the module.

In `@apps/mikeallisonjs.com/src/lib/agent/rate-limit.ts`:
- Around line 1-28: The current in-memory buckets Map used by checkRateLimit
(symbols: buckets, checkRateLimit, WINDOW_MS, LIMIT) is per-instance and never
evicts expired entries; fix it by (1) making checkRateLimit remove an entry when
existing.resetAt < now (delete from buckets before creating a fresh bucket) so
expired keys are not retained, and (2) if you need cross-instance enforcement
replace or back this Map with a shared store (Redis/Upstash/KV) rather than
per-instance state; if per-instance soft-guard behavior is intentional, add a
code comment near buckets explaining that limitation and why it's acceptable.
🪄 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: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: d5987562-ced7-4851-a065-6c62b3e0e6b3

📥 Commits

Reviewing files that changed from the base of the PR and between ee34f7c and e671b35.

📒 Files selected for processing (26)
  • apps/mikeallisonjs.com/.env.example
  • apps/mikeallisonjs.com/content/jobs/_template.md
  • apps/mikeallisonjs.com/content/jobs/agilix.md
  • apps/mikeallisonjs.com/content/jobs/jesusfilm.md
  • apps/mikeallisonjs.com/content/resume.md
  • apps/mikeallisonjs.com/index.d.ts
  • apps/mikeallisonjs.com/next-env.d.ts
  • apps/mikeallisonjs.com/next.config.mjs
  • apps/mikeallisonjs.com/src/app/api/agent/route.ts
  • apps/mikeallisonjs.com/src/app/global.css
  • apps/mikeallisonjs.com/src/app/layout.tsx
  • apps/mikeallisonjs.com/src/app/page.tsx
  • apps/mikeallisonjs.com/src/components/agent-terminal.tsx
  • apps/mikeallisonjs.com/src/components/browser-window.tsx
  • apps/mikeallisonjs.com/src/components/desktop-shell.tsx
  • apps/mikeallisonjs.com/src/components/header.tsx
  • apps/mikeallisonjs.com/src/components/hero.tsx
  • apps/mikeallisonjs.com/src/components/kde-panel.tsx
  • apps/mikeallisonjs.com/src/components/kde-window.tsx
  • apps/mikeallisonjs.com/src/components/portfolio.tsx
  • apps/mikeallisonjs.com/src/components/services.tsx
  • apps/mikeallisonjs.com/src/lib/agent/corpus.ts
  • apps/mikeallisonjs.com/src/lib/agent/openrouter.ts
  • apps/mikeallisonjs.com/src/lib/agent/projects.ts
  • apps/mikeallisonjs.com/src/lib/agent/rate-limit.ts
  • apps/mikeallisonjs.com/src/lib/agent/tools.ts

Comment thread apps/mikeallisonjs.com/content/jobs/agilix.md Outdated
Comment thread apps/mikeallisonjs.com/content/jobs/jesusfilm.md
Comment thread apps/mikeallisonjs.com/content/resume.md Outdated
Comment thread apps/mikeallisonjs.com/src/app/api/agent/route.ts
Comment thread apps/mikeallisonjs.com/src/app/api/agent/route.ts
Comment thread apps/mikeallisonjs.com/src/components/agent-terminal.tsx
Comment thread apps/mikeallisonjs.com/src/components/agent-terminal.tsx
Comment on lines +45 to +93
<button
className="flex h-7 w-7 items-center justify-center rounded-full text-[#7f8c8d] opacity-40"
aria-label="Back"
tabIndex={-1}
>
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
<path d="m15 18-6-6 6-6" />
</svg>
</button>
<button
className="flex h-7 w-7 items-center justify-center rounded-full text-[#7f8c8d] opacity-40"
aria-label="Forward"
tabIndex={-1}
>
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
<path d="m9 18 6-6-6-6" />
</svg>
</button>
<button
className="flex h-7 w-7 items-center justify-center rounded-full text-[#7f8c8d] transition-colors hover:bg-white/[0.06] hover:text-[#eff0f1]"
aria-label="Reload"
tabIndex={-1}
>
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
<path d="M3 12a9 9 0 1 0 9-9 9.75 9.75 0 0 0-6.74 2.74L3 8" />
<path d="M3 3v5h5" />
</svg>
</button>

{/* URL bar */}
<div className="flex flex-1 items-center gap-2 rounded-full bg-[#1e2226] px-3 py-[5px] font-mono text-[12px]">
<svg className="h-3.5 w-3.5 shrink-0 text-[#3daee9]" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
<rect width="18" height="11" x="3" y="11" rx="2" ry="2" />
<path d="M7 11V7a5 5 0 0 1 10 0v4" />
</svg>
<span className="truncate text-[#eff0f1]">{url}</span>
</div>

<button
className="flex h-7 w-7 items-center justify-center rounded-full text-[#7f8c8d] transition-colors hover:bg-white/[0.06] hover:text-[#eff0f1]"
aria-label="More"
tabIndex={-1}
>
<svg width="4" height="18" viewBox="0 0 4 20" fill="currentColor">
<circle cx="2" cy="2" r="2" />
<circle cx="2" cy="10" r="2" />
<circle cx="2" cy="18" r="2" />
</svg>
</button>
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Decorative browser controls should not be interactive buttons without behavior.

Right now these controls look interactive but don’t perform actions; that’s rough for keyboard/screen-reader users. Either wire real handlers (including keyboard behavior) or render them as non-interactive elements.

As per coding guidelines, "Implement accessibility features on interactive elements including tabindex, aria-label, onClick, and onKeyDown handlers".

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/mikeallisonjs.com/src/components/browser-window.tsx` around lines 45 -
93, The decorative browser control buttons (elements with aria-labels "Back",
"Forward", "Reload", "More" in the browser-window.tsx markup) are currently
interactive but have no behavior; either make them truly interactive by wiring
click and keyboard handlers or make them non-interactive decorative elements.
Fix option A: implement onClick handlers (e.g., handleBack, handleForward,
handleReload, handleMore) and corresponding onKeyDown to support Enter/Space,
keep tabIndex={0}, and ensure accessible labels remain. Fix option B: if they
are purely decorative, replace the <button> tags with non-interactive elements
(e.g., <span> or <div>) or add aria-hidden="true" and remove tabIndex to prevent
keyboard focus. Update the elements referenced by their aria-labels and the URL
span usage accordingly so the component behavior and accessibility match intent.

Comment on lines +72 to +93
<div className="absolute inset-0 flex items-center justify-center gap-3 bg-[color:var(--deep-space)]/70 opacity-0 backdrop-blur-sm transition-opacity duration-300 group-hover:opacity-100">
<Link
href={project.websiteUrl}
target="_blank"
rel="noopener noreferrer"
className="inline-flex items-center gap-1.5 rounded-md bg-[color:var(--spring-green)] px-4 py-2 font-mono text-xs font-medium text-white shadow-[0_0_20px_-4px_rgba(8,135,43,0.6)] transition-transform hover:scale-105"
>
<IconExternalLink size={14} />
visit
</Link>
{project.githubUrl && (
<Link
href={project.githubUrl}
target="_blank"
rel="noopener noreferrer"
className="inline-flex items-center gap-1.5 rounded-md border border-[color:var(--subtle-gray)] bg-black/50 px-4 py-2 font-mono text-xs font-medium text-[color:var(--polar-blue)] transition-colors hover:border-[color:var(--polar-blue)]/60"
>
<IconBrandGithub size={14} />
source
</Link>
)}
</div>
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Uh-oh, these action links are hover-only right now.

Line 72 hides controls behind group-hover only, which hurts keyboard and touch access. Make them visible by default on small screens and reveal on focus-within/hover for larger screens.

Proposed fix
-        <div className="absolute inset-0 flex items-center justify-center gap-3 bg-[color:var(--deep-space)]/70 opacity-0 backdrop-blur-sm transition-opacity duration-300 group-hover:opacity-100">
+        <div className="absolute inset-0 flex items-center justify-center gap-3 bg-[color:var(--deep-space)]/70 opacity-100 backdrop-blur-sm transition-opacity duration-300 md:opacity-0 md:group-hover:opacity-100 md:group-focus-within:opacity-100">
@@
-            className="inline-flex items-center gap-1.5 rounded-md bg-[color:var(--spring-green)] px-4 py-2 font-mono text-xs font-medium text-white shadow-[0_0_20px_-4px_rgba(8,135,43,0.6)] transition-transform hover:scale-105"
+            className="inline-flex items-center gap-1.5 rounded-md bg-[color:var(--spring-green)] px-4 py-2 font-mono text-xs font-medium text-white shadow-[0_0_20px_-4px_rgba(8,135,43,0.6)] transition-transform hover:scale-105 focus-visible:outline focus-visible:outline-2 focus-visible:outline-[color:var(--polar-blue)]"
@@
-              className="inline-flex items-center gap-1.5 rounded-md border border-[color:var(--subtle-gray)] bg-black/50 px-4 py-2 font-mono text-xs font-medium text-[color:var(--polar-blue)] transition-colors hover:border-[color:var(--polar-blue)]/60"
+              className="inline-flex items-center gap-1.5 rounded-md border border-[color:var(--subtle-gray)] bg-black/50 px-4 py-2 font-mono text-xs font-medium text-[color:var(--polar-blue)] transition-colors hover:border-[color:var(--polar-blue)]/60 focus-visible:outline focus-visible:outline-2 focus-visible:outline-[color:var(--polar-blue)]"

As per coding guidelines, "Implement accessibility features on interactive elements including tabindex, aria-label, onClick, and onKeyDown handlers".

📝 Committable suggestion

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

Suggested change
<div className="absolute inset-0 flex items-center justify-center gap-3 bg-[color:var(--deep-space)]/70 opacity-0 backdrop-blur-sm transition-opacity duration-300 group-hover:opacity-100">
<Link
href={project.websiteUrl}
target="_blank"
rel="noopener noreferrer"
className="inline-flex items-center gap-1.5 rounded-md bg-[color:var(--spring-green)] px-4 py-2 font-mono text-xs font-medium text-white shadow-[0_0_20px_-4px_rgba(8,135,43,0.6)] transition-transform hover:scale-105"
>
<IconExternalLink size={14} />
visit
</Link>
{project.githubUrl && (
<Link
href={project.githubUrl}
target="_blank"
rel="noopener noreferrer"
className="inline-flex items-center gap-1.5 rounded-md border border-[color:var(--subtle-gray)] bg-black/50 px-4 py-2 font-mono text-xs font-medium text-[color:var(--polar-blue)] transition-colors hover:border-[color:var(--polar-blue)]/60"
>
<IconBrandGithub size={14} />
source
</Link>
)}
</div>
<div className="absolute inset-0 flex items-center justify-center gap-3 bg-[color:var(--deep-space)]/70 opacity-100 backdrop-blur-sm transition-opacity duration-300 md:opacity-0 md:group-hover:opacity-100 md:group-focus-within:opacity-100">
<Link
href={project.websiteUrl}
target="_blank"
rel="noopener noreferrer"
className="inline-flex items-center gap-1.5 rounded-md bg-[color:var(--spring-green)] px-4 py-2 font-mono text-xs font-medium text-white shadow-[0_0_20px_-4px_rgba(8,135,43,0.6)] transition-transform hover:scale-105 focus-visible:outline focus-visible:outline-2 focus-visible:outline-[color:var(--polar-blue)]"
>
<IconExternalLink size={14} />
visit
</Link>
{project.githubUrl && (
<Link
href={project.githubUrl}
target="_blank"
rel="noopener noreferrer"
className="inline-flex items-center gap-1.5 rounded-md border border-[color:var(--subtle-gray)] bg-black/50 px-4 py-2 font-mono text-xs font-medium text-[color:var(--polar-blue)] transition-colors hover:border-[color:var(--polar-blue)]/60 focus-visible:outline focus-visible:outline-2 focus-visible:outline-[color:var(--polar-blue)]"
>
<IconBrandGithub size={14} />
source
</Link>
)}
</div>
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/mikeallisonjs.com/src/components/portfolio.tsx` around lines 72 - 93,
The overlay controls are only revealed via group-hover which breaks
keyboard/touch access; update the overlay div (the element using class
"group-hover:opacity-100") to be visible by default on small screens and only
hide/show by hover/focus on larger screens (e.g. use responsive Tailwind like
default opacity-100 and sm:opacity-0 sm:group-hover:opacity-100
sm:focus-within:opacity-100), and add accessibility attributes/handlers to the
action links (the Link elements rendering visit and source): include descriptive
aria-labels (e.g. aria-label={`Visit ${project.name}`} and aria-label={`View
source for ${project.name}`}), ensure each link is keyboard-focusable (anchors
are by default, but add tabindex if you changed element types), and attach
onClick and onKeyDown handlers that perform the same navigation behavior for
activation via keyboard. Reference the overlay div class
(group-hover:opacity-100) and the Link instances that use project.websiteUrl and
project.githubUrl to locate changes.

Comment on lines +93 to +95
export async function getJob(slug: string): Promise<JobRecord | null> {
if (!/^[a-z0-9-]+$/i.test(slug)) return null
return readJobFile(slug)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Slug lookup can miss valid jobs when callers pass uppercase.

Line 94 allows uppercase (/i), but the file read on Line 95 uses the raw slug; on case-sensitive filesystems this can return null for existing jobs. Normalize once (trim().toLowerCase()) before validation/read.

Patch idea
 export async function getJob(slug: string): Promise<JobRecord | null> {
-  if (!/^[a-z0-9-]+$/i.test(slug)) return null
-  return readJobFile(slug)
+  const normalizedSlug = slug.trim().toLowerCase()
+  if (!/^[a-z0-9-]+$/.test(normalizedSlug)) return null
+  return readJobFile(normalizedSlug)
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/mikeallisonjs.com/src/lib/agent/corpus.ts` around lines 93 - 95, getJob
may miss jobs on case-sensitive filesystems because it validates with a
case-insensitive regex but then passes the original slug to readJobFile;
normalize the slug first by calling trim().toLowerCase() and assign it to a
local variable, validate that normalizedSlug against /^[a-z0-9-]+$/ and then
call readJobFile(normalizedSlug) so both validation and file access use the same
canonical lowercased value (refer to getJob and readJobFile).

- Increased years of experience from 24 to 25 in the resume.
- Enhanced the summary to reflect a more comprehensive overview of skills and career highlights.
- Added a new specialization section detailing areas of expertise.
- Updated job title and responsibilities for the Agilix position, emphasizing leadership and contributions to the EdTech platform.
- Included new certifications and education details to provide a fuller picture of qualifications.

These changes aim to present a more accurate and detailed professional profile for Mike Allison.
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@apps/mikeallisonjs.com/content/jobs/emdeon.md`:
- Line 9: Update the front-matter "stack" array in emdeon.md to include the
missing technologies that appear in the body (add "JavaScript" and "CSS" to the
stack array) so the metadata matches the "## Tech" section; locate the "stack"
key in the front matter and append these entries (and repeat the same fix for
the other affected files referenced around lines 34-35) to keep search/agent
indexing consistent.
🪄 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: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 465249f9-83ac-48bb-98b6-57ed9b7df05b

📥 Commits

Reviewing files that changed from the base of the PR and between e671b35 and 9a7fcec.

📒 Files selected for processing (8)
  • apps/mikeallisonjs.com/content/jobs/agilix.md
  • apps/mikeallisonjs.com/content/jobs/captainssounds.md
  • apps/mikeallisonjs.com/content/jobs/compasslearning.md
  • apps/mikeallisonjs.com/content/jobs/cru.md
  • apps/mikeallisonjs.com/content/jobs/duthie.md
  • apps/mikeallisonjs.com/content/jobs/ecteon.md
  • apps/mikeallisonjs.com/content/jobs/emdeon.md
  • apps/mikeallisonjs.com/content/resume.md
✅ Files skipped from review due to trivial changes (6)
  • apps/mikeallisonjs.com/content/jobs/duthie.md
  • apps/mikeallisonjs.com/content/jobs/compasslearning.md
  • apps/mikeallisonjs.com/content/resume.md
  • apps/mikeallisonjs.com/content/jobs/cru.md
  • apps/mikeallisonjs.com/content/jobs/captainssounds.md
  • apps/mikeallisonjs.com/content/jobs/ecteon.md

Comment thread apps/mikeallisonjs.com/content/jobs/emdeon.md Outdated
- Introduced an AbortController to manage request cancellation, improving resource management and responsiveness.
- Updated the stream handling logic to check for abort signals, ensuring that the stream can be gracefully closed when needed.
- Refactored the send and close functions for better error handling and clarity.

These changes aim to enhance the reliability and performance of the agent's streaming functionality.
- Updated the technology stack for multiple job entries to include JavaScript and CSS, replacing CoffeeScript in Agilix and Ecteon, and removing it from the services component.
- Removed the Services component entirely from the project, streamlining the application structure and focusing on core functionalities.

These changes aim to enhance the accuracy of job descriptions and simplify the codebase.
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (2)
apps/mikeallisonjs.com/src/components/kde-panel.tsx (1)

47-47: ⚡ Quick win

Let’s ditch inline style props here and keep it Tailwind-only, champ.

Move these inline colors/border styles into Tailwind classes (or shared utility classes) to match the TSX styling rule used across this PR.

As per coding guidelines, "Always use Tailwind classes for styling HTML elements; avoid using CSS or style tags in React/Next.js components".

Also applies to: 54-55

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/mikeallisonjs.com/src/components/kde-panel.tsx` at line 47, The element
in kde-panel.tsx currently uses an inline style prop for background and
border-bottom; remove the style prop and replace it with Tailwind classes on the
same element (e.g., use className="bg-[`#1b1e20`] border-b border-[`#2d3136`]" or
equivalent configured color tokens) and do the same for the other inline style
occurrences referenced in this component (the other two style props noted).
Update any merging of existing className values accordingly so the component
(KDEPanel) uses only Tailwind utility classes instead of inline styles.
apps/mikeallisonjs.com/src/components/agent-terminal.tsx (1)

166-166: ⚡ Quick win

Dude, let’s move inline styles into Tailwind classes.

This component still uses style={...} in a couple spots, which breaks the repo’s TSX styling rule. Please replace them with Tailwind utilities (or a utility class in global styles if needed for the gradient).

As per coding guidelines, "Always use Tailwind classes for styling HTML elements; avoid using CSS or style tags in React/Next.js components".

Also applies to: 456-459

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/mikeallisonjs.com/src/components/agent-terminal.tsx` at line 166, The
div in the AgentTerminal component that currently uses style={{ paddingLeft:
indent.length * 12 }} (the element with key={i}) should be changed to use a
Tailwind utility class instead of inline styles — replace the inline style with
a dynamic Tailwind arbitrary value class like className={`flex gap-2
${`pl-[${indent.length * 12}px]`}`} so padding is driven by indent.length, and
similarly replace the other inline style(s) around lines 456-459 (the gradient
element) with either Tailwind utilities or a named global CSS utility (e.g.,
.agent-gradient) and reference that class in the component; locate these in the
AgentTerminal component and update className usages accordingly.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@apps/mikeallisonjs.com/src/components/kde-panel.tsx`:
- Around line 64-78: Buttons that hide their text at smaller viewports (the
navigation button rendering Icon + hidden label using `hidden sm:inline`, and
the mode-toggle icon button) lack accessible names and keyboard handlers; add an
explicit aria-label to each icon-only button (e.g., on the navigation <button>
that calls onNavigate(id) and on the mode-toggle button), ensure they are
focusable (tabIndex if necessary), and add onKeyDown handlers that mirror the
onClick behavior so keyboard users can activate them. Locate the navigation
button that uses onNavigate and Icon, and the mode-toggle icon button, and
update them to include aria-label values derived from `label` or the toggle
intent plus corresponding onKeyDown (Enter/Space) handlers and tabindex support.

---

Nitpick comments:
In `@apps/mikeallisonjs.com/src/components/agent-terminal.tsx`:
- Line 166: The div in the AgentTerminal component that currently uses style={{
paddingLeft: indent.length * 12 }} (the element with key={i}) should be changed
to use a Tailwind utility class instead of inline styles — replace the inline
style with a dynamic Tailwind arbitrary value class like className={`flex gap-2
${`pl-[${indent.length * 12}px]`}`} so padding is driven by indent.length, and
similarly replace the other inline style(s) around lines 456-459 (the gradient
element) with either Tailwind utilities or a named global CSS utility (e.g.,
.agent-gradient) and reference that class in the component; locate these in the
AgentTerminal component and update className usages accordingly.

In `@apps/mikeallisonjs.com/src/components/kde-panel.tsx`:
- Line 47: The element in kde-panel.tsx currently uses an inline style prop for
background and border-bottom; remove the style prop and replace it with Tailwind
classes on the same element (e.g., use className="bg-[`#1b1e20`] border-b
border-[`#2d3136`]" or equivalent configured color tokens) and do the same for the
other inline style occurrences referenced in this component (the other two style
props noted). Update any merging of existing className values accordingly so the
component (KDEPanel) uses only Tailwind utility classes instead of inline
styles.
🪄 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: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 883a51eb-ee66-4898-8982-23bab204de68

📥 Commits

Reviewing files that changed from the base of the PR and between 336b32d and f1f30fd.

📒 Files selected for processing (11)
  • apps/mikeallisonjs.com/content/jobs/agilix.md
  • apps/mikeallisonjs.com/content/jobs/compasslearning.md
  • apps/mikeallisonjs.com/content/jobs/cru.md
  • apps/mikeallisonjs.com/content/jobs/ecteon.md
  • apps/mikeallisonjs.com/content/jobs/emdeon.md
  • apps/mikeallisonjs.com/src/app/page.tsx
  • apps/mikeallisonjs.com/src/components/agent-terminal.tsx
  • apps/mikeallisonjs.com/src/components/desktop-shell.tsx
  • apps/mikeallisonjs.com/src/components/header.tsx
  • apps/mikeallisonjs.com/src/components/kde-panel.tsx
  • apps/mikeallisonjs.com/src/components/services.tsx
💤 Files with no reviewable changes (1)
  • apps/mikeallisonjs.com/src/components/services.tsx
✅ Files skipped from review due to trivial changes (4)
  • apps/mikeallisonjs.com/content/jobs/cru.md
  • apps/mikeallisonjs.com/content/jobs/compasslearning.md
  • apps/mikeallisonjs.com/content/jobs/agilix.md
  • apps/mikeallisonjs.com/content/jobs/ecteon.md
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/mikeallisonjs.com/src/components/header.tsx

Comment on lines +64 to +78
<button
key={id}
onClick={() => onNavigate(id)}
className={`flex h-8 items-center gap-1.5 rounded px-2.5 text-xs transition-colors ${
isActive
? 'bg-white/[0.1] text-[#eff0f1]'
: 'text-[#eff0f1] hover:bg-white/[0.06]'
}`}
>
<Icon
size={14}
className={`shrink-0 ${isActive ? 'text-[#3daee9]' : 'text-[#3daee9]'}`}
/>
<span className="hidden sm:inline">{label}</span>
</button>
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Buttons can become unlabeled for screen readers on smaller viewports.

When task text is hidden (hidden sm:inline), those controls are icon-only and need explicit accessible names. Same deal for the mode-toggle icon button: title is not a reliable accessible name. Please add aria-label for each button.

As per coding guidelines, "Implement accessibility features on interactive elements including tabindex, aria-label, onClick, and onKeyDown handlers".

Also applies to: 85-91

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/mikeallisonjs.com/src/components/kde-panel.tsx` around lines 64 - 78,
Buttons that hide their text at smaller viewports (the navigation button
rendering Icon + hidden label using `hidden sm:inline`, and the mode-toggle icon
button) lack accessible names and keyboard handlers; add an explicit aria-label
to each icon-only button (e.g., on the navigation <button> that calls
onNavigate(id) and on the mode-toggle button), ensure they are focusable
(tabIndex if necessary), and add onKeyDown handlers that mirror the onClick
behavior so keyboard users can activate them. Locate the navigation button that
uses onNavigate and Icon, and the mode-toggle icon button, and update them to
include aria-label values derived from `label` or the toggle intent plus
corresponding onKeyDown (Enter/Space) handlers and tabindex support.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant