Skip to content

Feature/agent streaming animation fixes#26

Merged
Ker102 merged 22 commits intomainfrom
feature/agent-streaming-animation-fixes
Mar 24, 2026
Merged

Feature/agent streaming animation fixes#26
Ker102 merged 22 commits intomainfrom
feature/agent-streaming-animation-fixes

Conversation

@Ker102
Copy link
Copy Markdown
Owner

@Ker102 Ker102 commented Mar 24, 2026

Description

Type of Change

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • Documentation update
  • Performance improvement
  • Code refactoring
  • Dependency update

Related Issues

Closes #

Motivation and Context

How Has This Been Tested?

  • Unit tests
  • Integration tests
  • Manual testing
  • End-to-end testing

Screenshots (if applicable)

Checklist

  • My code follows the project's code style guidelines
  • I have performed a self-review of my own code
  • I have commented my code, particularly in hard-to-understand areas
  • I have made corresponding changes to the documentation
  • My changes generate no new warnings or errors
  • I have added tests that prove my fix is effective or that my feature works
  • New and existing unit tests pass locally with my changes
  • Any dependent changes have been merged and published
  • I have checked my code and corrected any misspellings

Additional Notes

Summary by CodeRabbit

  • New Features

    • Image attachment support in chat and studio for reference-based generation.
    • Neural 3D Pipeline added (hosted APIs + serverless GPU options).
    • Interactive studio preview with tool category selector and hover details.
  • UI/UX Improvements

    • Rebranded UI to ViperMesh (site, desktop, and addon labels).
    • Redesigned navbar and animated logo/hero elements with enhanced visuals.
    • Collapsible agent activity summary for completed runs.
  • Updates

    • Platform docs updated (Next.js 16, ViperAgent 2.0, Gemini embeddings).
    • Improved RAG/follow-up query detection and expanded tool guides (lighting, rendering, spatial).

Ker102 added 21 commits March 23, 2026 04:11
Replace ~80 user-facing string references across 20 files:
- Landing pages (navbar, hero, footer, features)
- App metadata (layout title)
- Auth pages (login, signup, OAuth, electron callback)
- Chat UI (project-chat placeholder/buttons, studio advisor)
- Dashboard (nav, quick-start, MCP connection, local LLM)
- Content pages (docs, download, setup, addons, project detail)
- CSS design system comments

NOT changed (deferred for separate migration):
- window.modelforge Electron IPC API
- modelforge-addon.py download URLs
- --forge-* CSS variable prefix
…ename

- Electron bridge: preload.js key, electron.d.ts interface/Window prop
- Frontend consumers: login-form.tsx, electron-auth-listener.tsx, setup/page.tsx
- Desktop app: main.js env vars, protocol, title, HTML auth pages, addon paths
- Desktop package.json: name, appId, productName, copyright, dev script
- Addon: renamed modelforge-addon.py -> vipermesh-addon.py (desktop/assets + public/downloads)
- Addon internals: bl_info, User-Agent, panel classes, operator bl_idnames, sidebar category
- Download URLs: docs/page.tsx, quick-start-card.tsx
- Build verified: exit code 0, zero remaining modelforge refs in code
…ero panel

- Position viper illustration behind the hero StudioPreview panel
- Add clipped overlay copy (left half) that renders above the panel
- Create 3D weaving illusion: snake body goes under panel then emerges over it
- Move panel down (translateY: 100px) so snake curve clears above panel edge
- Fine-tune image size (185%), position (bottom: -95%, marginLeft: -90%)
- Add teal drop-shadow and fade-in animation to viper illustration
… and tooltips

- Replace generic hexagon icons with custom hand-crafted SVGs:
  Geometry (stacked layers), Texture (UV grid), Materials (shaded sphere),
  Lighting (sun rays), Animate (waveform)
- Leave 2nd box empty (covered by snake overlay)
- Add cursor-following tooltip showing tool description on hover
- Add hover scale and shadow effects on tool boxes
- Remove drop-shadow filter from snake image for performance
- Add useState import for tooltip tracking
- Add activeTool state to track selected tool (defaults to Geometry)
- Clicking a tool box highlights it with teal accent border and background
- Previously selected tool deactivates automatically
- Empty box (snake-covered) is not clickable
- Replace ModelForge branding with ViperMesh globally
- Add documentation for the new premium Teal design system
- Add documentation for the Autopilot and Studio dual modes
- Document the new Hybrid Neural 3D Pipeline (Hunyuan3D, TRELLIS 2, YVO3D)
- Update Quick Start and Roadmap to reflect current state
- Replace Hammer icon with AnimatedLogo on login page
- Replace Hammer icon with AnimatedLogo on signup page
- Replace old 'M' gradient square with ViperMesh logo on OAuth start page
- Replace old 'M' gradient square with ViperMesh logo on electron callback page
- Remove unused Hammer import from dashboard-nav
Renamed ~50 ModelForge references to ViperMesh in 13 doc files:
- docs/future-plans.md, architecture.md, architecture-notes.md
- docs/3d-pipeline-strategy.md, 3d-pipeline-integration.md
- docs/HANDOFF.md, research-pipeline-techniques.md, test-prompts.md
- docs/addon-integration-roadmap.md
- SETUP.md, SECURITY.md, deploy/runpod/README.md, gemini.md

Zero remaining ModelForge references in the entire project.
…reation)

- Test 17: multi-object fruit market stall with complex spatial relationships,
  directional sun + fill lighting, 7+ material types, camera framing
- Test 18: image-reference recreation using generated reading nook reference
  (tests vision analysis → Blender object translation)
- Added note marking Tests 14-16 as future-only (require neural models)
- Hide per-message TOOL CALLS during active streaming (only AgentActivity shows live)
- Thinking indicator only appears when agent is reasoning, not during tool calls
- AgentActivity collapses into closed details dropdown when done
- MCP execution summary now uses details element (closed by default)
- Expanded friendlyToolMap (7 -> 27 entries)
- Follow-up prompt uses grouped human-readable descriptions instead of raw tool names
- Fallback message uses grouped labels
- System prompt branding: ModelForge -> ViperMesh
…oning guide

- New section: Proximity & Contact Relationships
  - 'Leaning against' / 'Beside' (surface contact with margin)
  - 'Resting on' / 'Sitting on' (vertical contact)
  - 'Inside a container' (containment bounds)
  - 'Clustered' (minimum separation checks)
- Added scaled dimension awareness (radius × scale)
- Added safety margin reference table
- Added 3 new common mistakes (scale-aware offsets, surface-to-surface, intersection prevention)
- Exact watermelon/banana example from Test 17 bug
- Re-ingested 16 guides into vectorstore
- Add optional referenceImage input to blender-agent-shape in tool-catalog
- Wire functional file upload in studio-workspace.tsx (was decorative only)
  - File input reads image as base64 data URL
  - Shows thumbnail preview with remove button
- Forward image attachments in executeStep via studio-layout.tsx
  - Extracts image data from tool inputs as attachments array
  - Sends to /api/ai/chat in same format as Autopilot mode
- Add image attachment button to follow-up chat in step-session-drawer
  - Image icon button opens file picker
  - Shows pending image preview chip with remove
  - Sends attachments through onSendMessage callback
- route.ts: a.data from FileReader is already a full data URL
  (data:image/png;base64,...) — was wrapping it again causing
  Gemini to reject with 'Base64 decoding failed'
- step-session-drawer.tsx: show reference image thumbnail below
  the prompt text in the session drawer
- route.ts: use a.data directly (already a full data URL from FileReader)
  instead of wrapping with another data: prefix — fixes Gemini 400 error
- studio-layout.tsx: strip base64 image data from inputs before
  persisting steps to database (can be megabytes)
- step-session-drawer.tsx: detect data URLs in message content and
  render as <img> instead of raw base64 text; show reference image
  thumbnail below prompt
Image fixes:
- route.ts: use a.data directly (already full data URL) — fixes Gemini 400
- studio-layout.ts: strip base64 from persisted inputs (saves DB payload)
- step-session-drawer.tsx: detect data URLs in messages, render as <img>

RAG fix:
- agents.ts: detect short follow-up messages like 'try again' and use the
  original prompt for vector search instead of the vague text
- step-session-drawer.tsx: AgentActivity now renders in both running AND
  completed states (uses built-in <details> collapse). commandResults
  section only shown as legacy fallback when no agentEvents exist.
- blender-agent-system.md: explicit instruction to describe WHAT was
  built (objects, materials, composition) instead of listing tool counts
…ement

- lighting-guide.md: added LIGHT-EMITTING OBJECT GEOMETRY section with
  open shade rule, emission materials, point light placement checklist.
  Expanded tags for better RAG retrieval (lamp, candle, lantern, etc).
- render-guide.md: expanded film_transparent section with when-to-use
  vs when-NOT-to-use guidance (interior/exterior scenes = false).
- blender-agent-system.md: viewport screenshot is now MANDATORY at 3
  checkpoints (geometry, lighting, pre-render). Clarified that
  render_image does NOT return visual data.
- Re-ingested all 16 tool guides into vectorstore.
…, and use Gemini embeddings instead of Together.ai
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 24, 2026

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: bc90a54a-f4b1-4c81-a795-9b5dab50f269

📥 Commits

Reviewing files that changed from the base of the PR and between 4d4e380 and 086c7be.

📒 Files selected for processing (9)
  • components/landing/navbar.tsx
  • components/projects/agent-activity.tsx
  • components/projects/studio-layout.tsx
  • components/projects/studio-workspace.tsx
  • components/projects/workflow-timeline.tsx
  • data/tool-guides/spatial-positioning-guide.md
  • docs/test-prompts.md
  • gemini.md
  • lib/ai/agents.ts

📝 Walkthrough

Walkthrough

Rebrands ModelForge → ViperMesh across docs, web UI, desktop/electron, and prompts; adds image-attachment support end-to-end, RAG follow-up detection, interactive hero/navbar UI, new AnimatedLogo and LineShadowText components, studio image upload flow, and several UI/agent behavior adjustments.

Changes

Cohort / File(s) Summary
Documentation Rebrand
README.md, SECURITY.md, SETUP.md, docs/*, gemini.md, deploy/runpod/README.md
Replaced product name and related branding throughout docs and READMEs; updated some stack/model/version labels and roadmap items. No behavioral changes.
Web UI Text / Small UX
app/addons/page.tsx, app/docs/page.tsx, app/download/page.tsx, app/dashboard/projects/[id]/page.tsx, app/login/page.tsx, app/signup/page.tsx, app/auth/*, app/setup/page.tsx, components/*/local-llm-settings-card.tsx, components/*/mcp-connection-card.tsx, components/*/quick-start-card.tsx, components/landing/features.tsx, components/projects/studio-advisor.tsx
Mass rename of visible strings from ModelForge → ViperMesh, logo swaps, and minor JSX/icon replacements. No logic changes.
Branding: Layout/Header/Footer
app/layout.tsx, components/landing/footer.tsx, components/dashboard/dashboard-nav.tsx
Updated metadata title, swapped in AnimatedLogo, adjusted footer and nav branding/layout.
Animated Logo & New Text Component
components/ui/animated-logo.tsx, components/ui/line-shadow-text.tsx, tailwind.config.ts, app/globals.css
Added AnimatedLogo and LineShadowText components with motion/shine/line-shadow animations; added Tailwind keyframes/animation and updated CSS comments.
Hero & Navbar Redesign
components/landing/hero.tsx, components/landing/navbar.tsx
Reworked StudioPreview tool list to categorized tools with hover tooltips, active selection, mouse tracking; replaced sticky nav with centered fixed pill nav, hover pill animation, and motion CTAs.
Chat API & Agent Summaries
app/api/ai/chat/route.ts, lib/orchestration/prompts/index.ts, lib/orchestration/prompts/blender-agent-system.md
Attachment handling: use data URLs from client; replaced tool-name summarization with grouped human-readable labels/counts; updated follow-up/fallback system prompts and stricter visual-verification instructions in system prompt.
RAG / Agent Middleware
lib/ai/agents.ts
Added RETRY_PATTERNS and isShortFollowUp detection; when follow-ups are short, choose a substantive prior human message as RAG query fallback.
Agent Activity & Chat UI Behavior
components/projects/agent-activity.tsx, components/projects/project-chat.tsx
AgentActivity: collapsed "done" summary when inactive; live panel header simplified. Project chat: collapsible MCP execution details and refined "Thinking…" indicator based on in-flight tool calls.
Image Attachments & Studio Flow
components/projects/step-session-drawer.tsx, components/projects/studio-layout.tsx, components/projects/studio-workspace.tsx, lib/orchestration/tool-catalog.ts
Added image upload/preview/remove UI, pendingImage state, attachments parameter on send callbacks and executeStep; studio layout converts image inputs to attachments; tool-catalog adds optional referenceImage input.
Step/session state and persistence
components/projects/studio-layout.tsx, components/projects/step-session-drawer.tsx
Session persistence now strips monitoring logs and base64 images; workflowStepsRef added for execution consistency; send callback signature updated to accept attachments.
Desktop / Electron / Blender Addon
desktop/main.js, desktop/preload.js, desktop/package.json, desktop/assets/vipermesh-addon.py, public/downloads/vipermesh-addon.py, lib/types/electron.d.ts, components/auth/electron-auth-listener.tsx, components/auth/login-form.tsx
Renamed protocol/env vars/package/appId/productName; contextBridge exposes window.vipermesh; updated Electron UI copy and addon filename references; renamed Blender addon classes/operators and User-Agent strings.
Downloads / Public Addon
public/downloads/vipermesh-addon.py, desktop/assets/vipermesh-addon.py
Renamed Blender addon identifiers/operators and metadata; note: removed explicit request timeouts in public download script.
Tool Guides & Tests
data/tool-guides/*, docs/test-prompts.md
Expanded lighting, rendering, and spatial-positioning guides; added Tests 17–18 and a warning for tests relying on unintegrated neural models.
Types & Minor Types Update
lib/types/electron.d.ts, components/projects/workflow-timeline.tsx
Renamed Electron API interface to ViperMeshElectronAPI; extended StepCommandResult.status union to include "skipped".

Sequence Diagram(s)

sequenceDiagram
    participant User as User
    participant UI as Step Drawer UI
    participant FileAPI as FileReader
    participant Chat as /api/ai/chat
    participant LLM as LLM

    User->>UI: Select image file
    UI->>FileAPI: readAsDataURL(file)
    FileAPI-->>UI: data:image/...
    UI->>UI: store pendingImage
    User->>UI: Send message + image
    UI->>Chat: POST /api/ai/chat (message, attachments)
    Chat->>LLM: Process message + image data URL
    LLM-->>Chat: response (tool calls / streaming)
    Chat-->>UI: stream response
    UI->>UI: clear pendingImage, render result
Loading
sequenceDiagram
    participant User as User
    participant ChatComp as Chat Component
    participant Agent as Agent Middleware
    participant RAG as RAG Search
    participant LLM as LLM

    User->>ChatComp: Send short/vague follow-up
    ChatComp->>Agent: processMessage()
    Agent->>Agent: detect short follow-up (RETRY_PATTERNS)
    Agent->>Agent: select bestQuery from prior messages
    Agent->>RAG: search(bestQuery)
    RAG-->>Agent: context
    Agent->>LLM: inject context + prompt
    LLM-->>Agent: response
    Agent-->>ChatComp: deliver response
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested labels

scripts

Poem

🐰 I hopped through code and changed the name,

ModelForge shed scales — ViperMesh became.
Images hop in, queries find their cue,
Logos gleam and nav pills slide true.
A tiny rabbit cheers the new review.

🚥 Pre-merge checks | ❌ 3

❌ Failed checks (2 warnings, 1 inconclusive)

Check name Status Explanation Resolution
Description check ⚠️ Warning The PR description is entirely empty—all template sections are blank with no filled-in content, no change type selected, no motivation provided, and no testing information documented. Fill in the PR description template completely: describe what was changed and why, select the appropriate change type(s), link any related issues, explain the motivation/context, document testing performed, and complete the checklist items.
Docstring Coverage ⚠️ Warning Docstring coverage is 31.48% 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 is vague and generic, using 'Feature/agent streaming animation fixes' which is not descriptive enough. It lacks clarity about the main change—whether it's a rebrand, UI enhancement, animation improvement, or something else. Clarify the title to reflect the primary change. If the main change is the ModelForge → ViperMesh rebrand, use 'Rebrand: ModelForge to ViperMesh'. If UI/animation is primary, specify what was fixed. If multiple major changes, focus on the most impactful one.

✏️ 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 feature/agent-streaming-animation-fixes

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: 20

Caution

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

⚠️ Outside diff range comments (8)
app/globals.css (1)

28-44: 🧹 Nitpick | 🔵 Trivial

Consider renaming CSS variables for brand consistency.

The comment is updated to "ViperMesh Design System", but the CSS custom properties still use the --forge-* prefix (e.g., --forge-accent, --forge-surface). While this is an internal implementation detail that doesn't affect users, updating the variable names to align with the new brand (e.g., --viper-* or --vm-*) would improve codebase consistency.

This is non-blocking as it would require coordinated changes across multiple files that reference these variables.

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

In `@app/globals.css` around lines 28 - 44, Update the CSS custom property names
to match the new brand by renaming each --forge-* variable (e.g.,
--forge-accent, --forge-accent-hover, --forge-surface, --forge-border,
--forge-text, --forge-glass, --forge-shadow, etc.) to a consistent ViperMesh
prefix such as --viper-* or --vm-* and then update every reference across the
codebase to use the new names to avoid breakage (search for occurrences of the
specific symbols --forge-accent, --forge-accent-hover, --forge-accent-subtle,
--forge-accent-muted, --forge-surface, --forge-surface-dim,
--forge-surface-raised, --forge-border, --forge-border-strong, --forge-text,
--forge-text-muted, --forge-text-subtle, --forge-glass, --forge-glass-border,
--forge-shadow, --forge-shadow-lg and replace them with the chosen --viper- or
--vm- equivalents).
lib/orchestration/tool-catalog.ts (1)

1-6: ⚠️ Potential issue | 🟡 Minor

Missed branding update in JSDoc comment.

The file-level JSDoc comment still references "ModelForge tools" on line 2, which is inconsistent with the PR's rebranding to "ViperMesh".

Proposed fix
 /**
- * Tool Catalog — Central knowledge base for all ModelForge tools & categories.
+ * Tool Catalog — Central knowledge base for all ViperMesh tools & categories.
  *
  * Provides beginner-friendly metadata, input definitions, and descriptions
  * for every tool and pipeline category in the platform.
  */
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/orchestration/tool-catalog.ts` around lines 1 - 6, Update the file-level
JSDoc at the top of lib/orchestration/tool-catalog.ts to use the new branding:
change the phrase "ModelForge tools" to "ViperMesh tools" (and any other
"ModelForge" mentions in that same JSDoc block) so the comment reads "Tool
Catalog — Central knowledge base for all ViperMesh tools & categories." Locate
the top-of-file JSDoc comment (the block describing "Tool Catalog") and make the
textual replacement to reflect the rebranding.
SECURITY.md (1)

73-74: ⚠️ Potential issue | 🟡 Minor

Update SECURITY.md to reflect Supabase Auth migration.

Line 73 incorrectly references NextAuth.js, which has been fully removed from the project. The codebase now uses Supabase Auth as confirmed in docs/architecture.md (line 137) and evidenced by Supabase imports throughout the authentication layer.

📝 Suggested fix
 * Password hashing using bcryptjs with appropriate salt rounds
-* CSRF protection via NextAuth.js
+* CSRF protection via Supabase Auth
 * SQL injection prevention through Prisma ORM
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@SECURITY.md` around lines 73 - 74, Replace the outdated reference to
NextAuth.js in SECURITY.md with Supabase Auth: change the bullet "CSRF
protection via NextAuth.js" to something like "CSRF protection via Supabase Auth
(session and token handling provided by Supabase)" and ensure the document
references the current auth implementation (see docs/architecture.md and
Supabase imports in the auth layer) so the security entry accurately reflects
the migration.
SETUP.md (1)

26-26: 🧹 Nitpick | 🔵 Trivial

Database name still references modelforge in example connection strings.

Several example connection strings use modelforge as the database name (lines 26, 91-92, 220, 223). While the database name doesn't need to match the app name, this creates inconsistency with the rebrand. Consider updating to vipermesh for consistency, or add a note that the database name is configurable.

📝 Proposed fix for line 26
 # Database - Update with your PostgreSQL credentials
-DATABASE_URL="postgresql://user:password@localhost:5432/modelforge"
+DATABASE_URL="postgresql://user:password@localhost:5432/vipermesh"

Apply similar changes to lines 91, 92, 220, and 223.

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

In `@SETUP.md` at line 26, Update the example PostgreSQL connection strings that
still use the database name "modelforge" (specifically the DATABASE_URL example
and the other examples referenced on lines 26, 91-92, 220, 223) to use the new
brand-consistent database name "vipermesh" or alternatively add a short note
that the database name is configurable; locate and update the DATABASE_URL
example and any other explicit "modelforge" occurrences in SETUP.md to either
replace them with "vipermesh" or append a parenthetical like "(replace with your
database name)" to make it clear the name is configurable.
desktop/package.json (1)

106-110: ⚠️ Potential issue | 🟠 Major

Publish metadata still points at the pre-rebrand GitHub repo.

Line 109 still targets ModelForge, but this PR is in Ker102/ViperMesh. Electron Builder and updater metadata will be generated against the wrong GitHub repository.

Suggested fix
     "publish": {
       "provider": "github",
       "owner": "Ker102",
-      "repo": "ModelForge"
+      "repo": "ViperMesh"
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@desktop/package.json` around lines 106 - 110, Update the "publish" metadata
in the package.json publish block so the GitHub target points to the new repo:
change the "repo" value in the "publish" object (the keys "publish", "provider",
"owner", "repo") from "ModelForge" to "ViperMesh" so Electron Builder and
auto-update metadata are generated against Ker102/ViperMesh.
app/api/ai/chat/route.ts (2)

1068-1080: ⚠️ Potential issue | 🟠 Major

Don't overwrite the already-streamed assistant text in the empty fallback branch.

If the follow-up model returns zero chunks, assistantText = emptyFallback discards any response that was already streamed earlier in the request. The user saw that content live, but the Prisma record will only keep the fallback sentence after reload. Append the fallback the same way as the non-empty branch instead of replacing the buffer.

Suggested fix
             } else {
               // The LLM yielded 0 chunks — send a scene-aware fallback
               console.warn("[Chat] Follow-up stream returned empty text, sending scene-aware fallback")
               const sceneSummary = groupedLabels.length > 0
                 ? `I've set up your scene — ${groupedLabels.slice(0, 3).join(", ")}${groupedLabels.length > 3 ? ` and more` : ""}.`
                 : "I've set up the scene."
               const emptyFallback = overallSuccess
                 ? `${sceneSummary} What would you like to adjust or add next?`
                 : `Some steps didn't go as planned. Would you like me to retry with a different approach?`
-              send({ type: "followup_delta", delta: emptyFallback })
-              assistantText = emptyFallback
+              const fallbackDelta = assistantText.trim()
+                ? `\n\n${emptyFallback}`
+                : emptyFallback
+              send({ type: "followup_delta", delta: fallbackDelta })
+              assistantText = assistantText.trim()
+                ? `${assistantText.trimEnd()}\n\n${emptyFallback}`
+                : emptyFallback
             }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/api/ai/chat/route.ts` around lines 1068 - 1080, The empty-fallback branch
currently replaces any already-streamed assistant text by doing assistantText =
emptyFallback; instead, append the fallback like the non-empty branch to
preserve previously streamed content: build the appended text using
(assistantText || "").trimEnd() + "\n\n" + emptyFallback and assign that to
assistantText, and continue sending the followup_delta via send({ type:
"followup_delta", delta: emptyFallback }); refer to assistantText, followUpText,
groupedLabels and overallSuccess in the existing branch to locate and update the
logic.

799-809: ⚠️ Potential issue | 🟠 Major

Add bounds and format validation for image attachments before forwarding to the provider.

The attachment schema (line 270) accepts a data field with no size, count, or MIME type constraints. After the full JSON body is materialized via req.json(), attachments are forwarded directly at lines 804–809 without validation. Add:

  • Maximum attachment count limit
  • Maximum decoded image size (e.g., 5–20 MB per image)
  • MIME type validation (restrict to data:image/*;base64,...)
  • Total payload cap to prevent memory exhaustion before reaching the provider
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/api/ai/chat/route.ts` around lines 799 - 809, Validate and sanitize image
attachments before building messageContent: before the mapping that uses
attachments and a.data (the hasImageAttachments branch using agentPrompt and
attachments.map), enforce a max attachments count (e.g., 5), validate each
a.data matches the data URL MIME pattern /^data:image\/[a-zA-Z0-9.+-]+;base64,/,
decode and check each image's decoded size does not exceed the per-image limit
(e.g., 5–20MB) and track a running total to enforce a global payload cap to
avoid memory exhaustion; if any check fails, short-circuit with a clear 4xx
error response instead of forwarding, and only build messageContent (the array
used for provider forwarding) after all attachments pass validation.
README.md (1)

162-177: ⚠️ Potential issue | 🟡 Minor

Finish the Quick Start rebrand in the clone step.

This installation section was updated for ViperMesh, but the clone command still points users at ModelForge and tells them to cd into that old directory name. Update the repository path and folder name here so the onboarding flow is internally consistent.

Also applies to: 186-188

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

In `@README.md` around lines 162 - 177, Update the README clone/install steps to
use the new ViperMesh branding: change the git clone URL and the cd target from
"ModelForge" to "ViperMesh" in the clone step shown around the current block
(the lines with "git clone https://github.com/Ker102/ModelForge.git" and "cd
ModelForge") and also update the identical occurrence referenced at lines
~186-188 so both places consistently point to
"https://github.com/Ker102/ViperMesh.git" and "cd ViperMesh".
🤖 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/auth/start-oauth/page.tsx`:
- Around line 54-57: Replace the native img tag in the start-oauth page
component with Next.js's optimized Image: add an import for Image from
"next/image" at the top of the component file and swap the <img ... /> element
inside the div (the element rendering "/vipermesh-logo-transparent.png") for an
<Image> usage that supplies width and height (64 each) and retains the
"object-contain" styling and alt text; ensure the file remains a client
component so the Image import is allowed.

In `@components/landing/hero.tsx`:
- Around line 184-205: The tool tiles rendered in tools.map should be real
buttons to provide keyboard focus and activation: replace the outer <div> used
in the mapping with a <button type="button"> while preserving existing behavior
from setActiveTool, handleMouseMove, setTooltip, and tool.icon; add
aria-pressed={isActive} to expose selected state to assistive tech; keep the
conditional cursor/hover classes but also add matching focus/focus-visible
styles so keyboard focus mirrors hover (e.g., same
scale/shadow/border/background changes when focused); ensure the key prop
(tool.name || `empty-${idx}`) remains and that click activation continues to
call setActiveTool(tool.name).

In `@components/landing/navbar.tsx`:
- Around line 66-94: The CTAs use motion.button inside Link which creates nested
interactive elements; change both motion.button usages (the "Log in" and "Get
Started" CTA blocks) to non-interactive anchors by rendering motion.a (or using
Link with an <a> child) instead of motion.button, keep the same className,
motion props (whileHover/whileTap) and the inner motion.div for the animated
sheen, and add an appropriate aria-label if the text isn't sufficient;
alternatively wrap Link around a non-interactive motion.div and put the anchor
inside, but do not nest a <button> inside Link to resolve the
accessibility/focus issues.

In `@components/projects/agent-activity.tsx`:
- Around line 146-208: The collapsed “Done” branch currently treats any terminal
non-active state the same and hides failures; update the conditional rendering
around isActive so terminal failure cases are handled separately: keep the
existing Done summary only when !isActive && completedCount > 0, add a new
branch for !isActive && completedCount === 0 && failedCount > 0 (or derive
failed entries from allCompleted/allFailed) that renders a failure summary and
lists failed tools (use a different ToolIcon status like "failed" and list from
allFailed), and for mixed results when !isActive && failedCount > 0 &&
completedCount > 0 render a combined summary that shows both success and failure
lists; ensure these branches short-circuit so the live “Agent working” header
never renders after terminal failure.

In `@components/projects/project-chat.tsx`:
- Around line 1481-1493: The current logic builds a Set<string> (startedTools)
from agentEvents and toggles presence by tool name, which fails when multiple
overlapping calls for the same tool exist; replace the Set with a Map<string,
number> (or a per-tool counter) and iterate agentEvents to increment the count
for a tool on a "started" event and decrement on completion/failure/finished
events, removing the entry when the count reaches zero; then compute
hasActiveTool by checking if any map value > 0 (or the total sum > 0) and keep
isThinking = agentActive && !hasActiveTool, using the existing agentEvents,
startedTools (renamed), hasActiveTool and isThinking identifiers to locate where
to change the logic.

In `@components/projects/studio-layout.tsx`:
- Around line 36-49: The current slimming logic in the steps.map (variable slim)
strips data:image/... payloads by replacing them with "[image attached]", which
loses the actual image needed to rebuild attachments and breaks
StepSessionDrawer previews and calls to /api/ai/chat; update the logic in the
map callback to either (A) persist a durable attachment reference/thumbnail
instead of the raw base64 (e.g., store an attachments handle/id or small
thumbnail string alongside inputs so the image can be rehydrated later), or (B)
if no durable handle exists, mark the restored step as non-runnable (add a flag
like inputs._runnable = false or step.meta.runnable = false) so callers
(StepSessionDrawer and the code that posts to /api/ai/chat) know not to attempt
to use a missing image; ensure the code that reads slim can find the new
attachment handle/thumbnail key and that existing callers check the runnable
flag before sending images.
- Around line 402-403: The complete event handler in executeStep closes over a
stale workflowSteps causing appendCommandResult to mis-handle batch results; fix
by introducing a ref (e.g., workflowStepsRef) that is updated whenever
workflowSteps changes and read workflowStepsRef.current inside the "complete"
handler (instead of direct workflowSteps) so the closure always sees the latest
state; update the ref in the same component where workflowSteps is defined (use
useRef and a useEffect or assignment on render) and leave executeStep's
dependency array as-is while replacing reads of workflowSteps with
workflowStepsRef.current (also update any related checks such as hasLiveResults
to use the ref).

In `@components/projects/studio-workspace.tsx`:
- Around line 457-465: Add file size validation and FileReader error handling to
the image upload handler: before creating the FileReader, check
e.target.files?.[0].size against a defined MAX_IMAGE_BYTES (e.g., 2MB) and
early-return with a user-visible error (set state or call a provided error
handler) if too large; attach reader.onerror to handle read failures (set the
same error state and avoid calling setInputs) and keep reader.onload to
setInputs({ ...inputs, [input.key]: reader.result as string }) only on
successful read; ensure any error messages reference the input key or context
for clarity.
- Around line 403-469: The image file input isn't being reset after removal so
re-selecting the same file may not fire onChange; update the hidden <input> (the
one with type="file" and onChange that uses FileReader and setInputs) to clear
its value on click (e.g., add an onClick handler that sets e.currentTarget.value
= ''), and ensure the remove button that calls setInputs({ ...inputs,
[input.key]: "" }) remains as-is so the input is truly cleared both in state and
DOM before the next selection.

In `@data/tool-guides/spatial-positioning-guide.md`:
- Around line 145-164: The markdown around the "CRITICAL: Scaled Dimensions"
section and following code blocks needs whitespace and fenced-block fixes to
satisfy the linter: add a blank line before and after the "### CRITICAL: Scaled
Dimensions" and the "### \"Leaning Against\" / \"Beside\" (Surface Contact)"
headings, ensure the fenced code blocks containing the lines "actual_extent_x =
R × sx    (NOT just R)" and "half_x = (S / 2) × sx" are fenced with ```python
(language specifier) and have a blank line both before the opening fence and
after the closing fence, and add blank lines around any inline tables or other
block elements (e.g., the "ALWAYS multiply dimensions..." paragraph) so the
section meets MD022, MD031/MD040, and MD058 requirements.

In `@desktop/assets/vipermesh-addon.py`:
- Around line 21-30: The bl_info metadata still points doc_url to the old
ModelForge repo; update bl_info["doc_url"] (in the bl_info dict) to the renamed
project's URL (the ViperMesh repo/homepage) so the addon details link in Blender
points to the correct, current project documentation. Ensure the string value is
replaced with the new canonical project URL.

In `@docs/test-prompts.md`:
- Around line 411-435: The test references
docs/test18-reading-nook-reference.png but never attaches it, so upload or embed
that reference image and update the prompt to point to the included asset;
specifically, add the image file into the repo (or specify an uploaded tester
artifact) and change the prompt block to include the image inline (e.g.,
markdown image tag referencing docs/test18-reading-nook-reference.png) and add a
short sentence that the tester will supply the image as input so the agent can
call get_viewport_screenshot/use vision — update the test verification text
accordingly to state the agent will receive the image and therefore exercise the
vision path.
- Around line 370-425: The markdown for the new "### Test 17: Multi-Object Scene
with Complex Spatial Relationships & Lighting" and "### Test 18: Image Reference
Recreation (Vision + Spatial)" sections needs lint fixes: add a blank line
before and after each heading and before/after every fenced code block, and
annotate the fences with a language (use ```text for the prompt blocks) so
MD022/MD031/MD040 are resolved; update the fenced blocks in both Test 17 and
Test 18 (the prompt/code fences shown in the diff) to include the language spec
and ensure there's an empty line separating the fences from surrounding
paragraphs/headings.

In `@gemini.md`:
- Around line 138-139: Move the two completed items ("Added glassmorphism
floating pill navbar with magnetic hover pills and spring CTA animations." and
"Added LineShadowText component (Magic UI) to '3D Models' hero text with teal
shadow.") out of the "Future Implementation Plans" section and append them to
the correct session log entry above; locate the "Future Implementation Plans"
heading and the nearest preceding session log entry in gemini.md and place these
lines under that session log so they reflect completed work rather than future
plans.
- Around line 3-10: Fix the Markdown heading spacing by ensuring there is a
blank line before and after each top-level or subheading mentioned (e.g., the
"Last Session: 2026-03-24 (Recent Changes)" heading, the "What Was Done"
subheading, and other headings that follow like the sections around "README
Updates", "What Was Done" items, and subsequent section headers); insert a
single empty line above each heading and a single empty line between the heading
and the paragraph or list that follows so the document conforms to markdownlint
MD022 and MD024 style rules.

In `@lib/ai/agents.ts`:
- Around line 1102-1128: The current logic marks any short lastContent as a
retry and then picks the longest prior human message (RETRY_PATTERNS,
isShortFollowUp, allHumanMessages, bestQuery), which can pull unrelated context;
change isShortFollowUp so it only triggers for explicit retry patterns or truly
negligible edits (e.g. match RETRY_PATTERNS OR lastContent.length < a very small
threshold) rather than every short message, and when isShortFollowUp is true,
iterate messages in reverse (or reverse allHumanMessages) to select the most
recent prior human message whose content length > 30 and that does not itself
match RETRY_PATTERNS, then set content to that recent substantive message
instead of the longest one.

In `@lib/orchestration/prompts/blender-agent-system.md`:
- Around line 13-17: The few-shot examples in blender-agent-system.md contradict
the "Visual Confirmation — MANDATORY" rule; update each example sequence so
every example calls get_viewport_screenshot after creating main
geometry/objects, after setting up lighting, and immediately before calling
render_image, removing any flows that skip those checkpoints and ensuring the
examples mirror the exact phrasing and ordering of the rule (referencing
get_viewport_screenshot and render_image) so no counterexamples remain.

In `@public/downloads/vipermesh-addon.py`:
- Around line 21-30: Update the bl_info dictionary entry doc_url in
vipermesh-addon.py so it no longer points to the old ModelForge repo; locate the
bl_info symbol and change the "doc_url" value to the correct ViperMesh
documentation or repo URL (e.g., the ViperMesh GitHub or docs page) to keep
branding consistent.
- Line 1438: The requests.get() calls to the Poly Haven API currently have no
timeout and can hang Blender; update each call to include an appropriate
timeout: add timeout=30 for lightweight API endpoints (e.g., the call in
response = requests.get(f"https://api.polyhaven.com/categories/{asset_type}",
...) and the functions search_polyhaven_assets and related API queries) and use
timeout=60 for larger file downloads (e.g., download_polyhaven_asset, the
texture download, model download, and include file download functions) so
network requests fail fast and don’t block the UI.

In `@README.md`:
- Around line 52-58: The new markdown headings (e.g., "### 🌐 Premium Web
Dashboard UI") lack surrounding blank lines causing markdownlint MD022; add a
single blank line before and after each heading and the same for the other
flagged headings mentioned (lines containing "Dual Modes", "Teal Design System",
"Supabase Auth" and the sections at 66-76 and 94-99) so each heading is
separated from preceding and following content and the MD022 violations are
resolved.

---

Outside diff comments:
In `@app/api/ai/chat/route.ts`:
- Around line 1068-1080: The empty-fallback branch currently replaces any
already-streamed assistant text by doing assistantText = emptyFallback; instead,
append the fallback like the non-empty branch to preserve previously streamed
content: build the appended text using (assistantText || "").trimEnd() + "\n\n"
+ emptyFallback and assign that to assistantText, and continue sending the
followup_delta via send({ type: "followup_delta", delta: emptyFallback }); refer
to assistantText, followUpText, groupedLabels and overallSuccess in the existing
branch to locate and update the logic.
- Around line 799-809: Validate and sanitize image attachments before building
messageContent: before the mapping that uses attachments and a.data (the
hasImageAttachments branch using agentPrompt and attachments.map), enforce a max
attachments count (e.g., 5), validate each a.data matches the data URL MIME
pattern /^data:image\/[a-zA-Z0-9.+-]+;base64,/, decode and check each image's
decoded size does not exceed the per-image limit (e.g., 5–20MB) and track a
running total to enforce a global payload cap to avoid memory exhaustion; if any
check fails, short-circuit with a clear 4xx error response instead of
forwarding, and only build messageContent (the array used for provider
forwarding) after all attachments pass validation.

In `@app/globals.css`:
- Around line 28-44: Update the CSS custom property names to match the new brand
by renaming each --forge-* variable (e.g., --forge-accent, --forge-accent-hover,
--forge-surface, --forge-border, --forge-text, --forge-glass, --forge-shadow,
etc.) to a consistent ViperMesh prefix such as --viper-* or --vm-* and then
update every reference across the codebase to use the new names to avoid
breakage (search for occurrences of the specific symbols --forge-accent,
--forge-accent-hover, --forge-accent-subtle, --forge-accent-muted,
--forge-surface, --forge-surface-dim, --forge-surface-raised, --forge-border,
--forge-border-strong, --forge-text, --forge-text-muted, --forge-text-subtle,
--forge-glass, --forge-glass-border, --forge-shadow, --forge-shadow-lg and
replace them with the chosen --viper- or --vm- equivalents).

In `@desktop/package.json`:
- Around line 106-110: Update the "publish" metadata in the package.json publish
block so the GitHub target points to the new repo: change the "repo" value in
the "publish" object (the keys "publish", "provider", "owner", "repo") from
"ModelForge" to "ViperMesh" so Electron Builder and auto-update metadata are
generated against Ker102/ViperMesh.

In `@lib/orchestration/tool-catalog.ts`:
- Around line 1-6: Update the file-level JSDoc at the top of
lib/orchestration/tool-catalog.ts to use the new branding: change the phrase
"ModelForge tools" to "ViperMesh tools" (and any other "ModelForge" mentions in
that same JSDoc block) so the comment reads "Tool Catalog — Central knowledge
base for all ViperMesh tools & categories." Locate the top-of-file JSDoc comment
(the block describing "Tool Catalog") and make the textual replacement to
reflect the rebranding.

In `@README.md`:
- Around line 162-177: Update the README clone/install steps to use the new
ViperMesh branding: change the git clone URL and the cd target from "ModelForge"
to "ViperMesh" in the clone step shown around the current block (the lines with
"git clone https://github.com/Ker102/ModelForge.git" and "cd ModelForge") and
also update the identical occurrence referenced at lines ~186-188 so both places
consistently point to "https://github.com/Ker102/ViperMesh.git" and "cd
ViperMesh".

In `@SECURITY.md`:
- Around line 73-74: Replace the outdated reference to NextAuth.js in
SECURITY.md with Supabase Auth: change the bullet "CSRF protection via
NextAuth.js" to something like "CSRF protection via Supabase Auth (session and
token handling provided by Supabase)" and ensure the document references the
current auth implementation (see docs/architecture.md and Supabase imports in
the auth layer) so the security entry accurately reflects the migration.

In `@SETUP.md`:
- Line 26: Update the example PostgreSQL connection strings that still use the
database name "modelforge" (specifically the DATABASE_URL example and the other
examples referenced on lines 26, 91-92, 220, 223) to use the new
brand-consistent database name "vipermesh" or alternatively add a short note
that the database name is configurable; locate and update the DATABASE_URL
example and any other explicit "modelforge" occurrences in SETUP.md to either
replace them with "vipermesh" or append a parenthetical like "(replace with your
database name)" to make it clear the name is configurable.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 0d282faf-dcc4-4b36-8203-e2fe38cd5673

📥 Commits

Reviewing files that changed from the base of the PR and between 73e05c8 and 4d4e380.

⛔ Files ignored due to path filters (9)
  • Modify_wireframe_opacity_202603230424.jpeg is excluded by !**/*.jpeg
  • Modify_wireframe_opacity_202603230425.jpeg is excluded by !**/*.jpeg
  • docs/test18-reading-nook-reference.png is excluded by !**/*.png
  • public/images/c815e7cf-ee13-48ff-a8a9-2189ac48a424.png is excluded by !**/*.png
  • public/vipermesh-logo-image.jpeg is excluded by !**/*.jpeg
  • public/vipermesh-logo-transparent.png is excluded by !**/*.png
  • public/vipermesh-logo-v1.svg is excluded by !**/*.svg
  • public/vipermesh-logo-v2.svg is excluded by !**/*.svg
  • public/vipermesh-logo.svg is excluded by !**/*.svg
📒 Files selected for processing (58)
  • README.md
  • SECURITY.md
  • SETUP.md
  • app/addons/page.tsx
  • app/api/ai/chat/route.ts
  • app/auth/electron-callback/page.tsx
  • app/auth/start-oauth/page.tsx
  • app/dashboard/projects/[id]/page.tsx
  • app/docs/page.tsx
  • app/download/page.tsx
  • app/globals.css
  • app/layout.tsx
  • app/login/page.tsx
  • app/setup/page.tsx
  • app/signup/page.tsx
  • components/auth/electron-auth-listener.tsx
  • components/auth/login-form.tsx
  • components/dashboard/dashboard-nav.tsx
  • components/dashboard/local-llm-settings-card.tsx
  • components/dashboard/mcp-connection-card.tsx
  • components/dashboard/quick-start-card.tsx
  • components/landing/features.tsx
  • components/landing/footer.tsx
  • components/landing/hero.tsx
  • components/landing/navbar.tsx
  • components/projects/agent-activity.tsx
  • components/projects/project-chat.tsx
  • components/projects/step-session-drawer.tsx
  • components/projects/studio-advisor.tsx
  • components/projects/studio-layout.tsx
  • components/projects/studio-workspace.tsx
  • components/ui/animated-logo.tsx
  • components/ui/line-shadow-text.tsx
  • data/tool-guides/lighting-guide.md
  • data/tool-guides/render-guide.md
  • data/tool-guides/spatial-positioning-guide.md
  • deploy/runpod/README.md
  • desktop/assets/vipermesh-addon.py
  • desktop/main.js
  • desktop/package.json
  • desktop/preload.js
  • docs/3d-pipeline-integration.md
  • docs/3d-pipeline-strategy.md
  • docs/HANDOFF.md
  • docs/addon-integration-roadmap.md
  • docs/architecture-notes.md
  • docs/architecture.md
  • docs/future-plans.md
  • docs/research-pipeline-techniques.md
  • docs/test-prompts.md
  • gemini.md
  • lib/ai/agents.ts
  • lib/orchestration/prompts/blender-agent-system.md
  • lib/orchestration/prompts/index.ts
  • lib/orchestration/tool-catalog.ts
  • lib/types/electron.d.ts
  • public/downloads/vipermesh-addon.py
  • tailwind.config.ts

Comment on lines +54 to +57
<div className="w-16 h-16 mx-auto flex items-center justify-center">
<img src="/vipermesh-logo-transparent.png" alt="ViperMesh" className="w-16 h-16 object-contain" />
</div>
<h1 className="mt-4 text-xl font-semibold text-white">ModelForge</h1>
<h1 className="mt-4 text-xl font-semibold text-white">ViperMesh</h1>
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Check that the ViperMesh logo file exists in public directory

fd -t f "vipermesh-logo-transparent.png" public

Repository: Ker102/ViperMesh

Length of output: 97


🏁 Script executed:

head -60 app/auth/start-oauth/page.tsx

Repository: Ker102/ViperMesh

Length of output: 2490


Consider using Next.js Image component for optimized loading.

The native <img> tag works, but doesn't leverage Next.js image optimization. The file is already a client component, so using the Image component from next/image is straightforward. Update the import and replace the img tag:

import Image from "next/image"
// ...
<Image src="/vipermesh-logo-transparent.png" alt="ViperMesh" width={64} height={64} className="object-contain" />
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/auth/start-oauth/page.tsx` around lines 54 - 57, Replace the native img
tag in the start-oauth page component with Next.js's optimized Image: add an
import for Image from "next/image" at the top of the component file and swap the
<img ... /> element inside the div (the element rendering
"/vipermesh-logo-transparent.png") for an <Image> usage that supplies width and
height (64 each) and retains the "object-contain" styling and alt text; ensure
the file remains a client component so the Image import is allowed.

Comment on lines 184 to +205
<div className="grid grid-cols-6 gap-1.5 mb-4">
{tools.map((tool) => (
<div key={tool.name}
className="aspect-square rounded-lg border flex flex-col items-center justify-center gap-1"
{tools.map((tool, idx) => {
const isActive = tool.name === activeTool
return (
<div key={tool.name || `empty-${idx}`}
className={`aspect-square rounded-lg border flex flex-col items-center justify-center gap-1 ${tool.desc ? 'cursor-pointer transition-all duration-200 hover:scale-105 hover:shadow-md' : ''}`}
style={{
borderColor: tool.active ? "hsl(var(--forge-accent))" : "hsl(var(--forge-border))",
backgroundColor: tool.active ? "hsl(var(--forge-accent-subtle))" : "hsl(var(--forge-surface-dim))",
}}>
<svg width="16" height="16" viewBox="0 0 24 24" fill="none"
stroke={tool.active ? "hsl(var(--forge-accent))" : "hsl(var(--forge-text-subtle))"} strokeWidth="1.5">
<path d="M12 3l8 4.5v9L12 21l-8-4.5v-9L12 3z" />
</svg>
<span className="text-[8px]" style={{
color: tool.active ? "hsl(var(--forge-accent))" : "hsl(var(--forge-text-subtle))",
}}>{tool.name}</span>
borderColor: isActive ? "hsl(var(--forge-accent))" : "hsl(var(--forge-border))",
backgroundColor: isActive ? "hsl(var(--forge-accent-subtle))" : "hsl(var(--forge-surface-dim))",
}}
onClick={() => tool.name && setActiveTool(tool.name)}
onMouseMove={(e) => handleMouseMove(e, tool.desc)}
onMouseLeave={() => setTooltip(null)}
>
{tool.icon(isActive ? "hsl(var(--forge-accent))" : "hsl(var(--forge-text-subtle))")}
{tool.name && (
<span className="text-[8px]" style={{
color: isActive ? "hsl(var(--forge-accent))" : "hsl(var(--forge-text-subtle))",
}}>{tool.name}</span>
)}
</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 | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

# First, let's examine the file and the specific lines mentioned
wc -l components/landing/hero.tsx

Repository: Ker102/ViperMesh

Length of output: 91


🏁 Script executed:

# Read the relevant section with context
sed -n '175,210p' components/landing/hero.tsx | cat -n

Repository: Ker102/ViperMesh

Length of output: 2271


🏁 Script executed:

# Check the entire tool rendering and state management
sed -n '180,220p' components/landing/hero.tsx | cat -n

Repository: Ker102/ViperMesh

Length of output: 2670


🏁 Script executed:

# Look for any keyboard event handlers in the component
rg -A 5 -B 5 "onKey" components/landing/hero.tsx

Repository: Ker102/ViperMesh

Length of output: 42


🏁 Script executed:

# Check for any aria attributes being used in the file
rg "aria-" components/landing/hero.tsx

Repository: Ker102/ViperMesh

Length of output: 121


🏁 Script executed:

# Look at the parent grid and any keyboard handling context
sed -n '160,230p' components/landing/hero.tsx | cat -n

Repository: Ker102/ViperMesh

Length of output: 4389


🏁 Script executed:

# Search for any keyboard event handlers that might apply to this section
rg -B 15 "tools.map" components/landing/hero.tsx | head -50

Repository: Ker102/ViperMesh

Length of output: 689


🏁 Script executed:

# Check if the parent div has any role or keyboard delegation
rg "role=" components/landing/hero.tsx

Repository: Ker102/ViperMesh

Length of output: 42


🏁 Script executed:

# Check for tabindex in the file
rg "tabIndex" components/landing/hero.tsx

Repository: Ker102/ViperMesh

Length of output: 42


The selectable tool tiles need button semantics and keyboard support.

These tiles are interactive controls (clickable with onClick, managing activeTool state), but they're rendered as plain divs with no keyboard handlers or accessibility attributes. Keyboard users cannot focus, activate, or navigate these controls, and assistive tech gets no indication of the selected state. Convert them to button type="button" elements, add aria-pressed={isActive} to indicate selection, and ensure focus/blur styles mirror the hover behavior for visual consistency across input methods.

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

In `@components/landing/hero.tsx` around lines 184 - 205, The tool tiles rendered
in tools.map should be real buttons to provide keyboard focus and activation:
replace the outer <div> used in the mapping with a <button type="button"> while
preserving existing behavior from setActiveTool, handleMouseMove, setTooltip,
and tool.icon; add aria-pressed={isActive} to expose selected state to assistive
tech; keep the conditional cursor/hover classes but also add matching
focus/focus-visible styles so keyboard focus mirrors hover (e.g., same
scale/shadow/border/background changes when focused); ensure the key prop
(tool.name || `empty-${idx}`) remains and that click activation continues to
call setActiveTool(tool.name).

Comment on lines 66 to 94
<Link href="/login">
<Button
variant="ghost"
className="text-sm font-medium rounded-full px-5"
style={{ color: "hsl(var(--forge-text-muted))" }}
<motion.button
whileHover={{ scale: 1.05, backgroundColor: "rgba(0,0,0,0.05)" }}
whileTap={{ scale: 0.95 }}
className="text-sm font-medium rounded-full px-5 py-2.5 text-muted-foreground hover:text-foreground transition-colors"
>
Log in
</Button>
</motion.button>
</Link>
<Link href="/signup">
<Button
className="text-sm font-semibold rounded-full px-6"
<motion.button
whileHover={{ scale: 1.05, filter: "brightness(1.1)" }}
whileTap={{ scale: 0.95 }}
className="text-sm font-semibold rounded-full px-6 py-2.5 relative overflow-hidden group"
style={{
backgroundColor: "hsl(var(--forge-accent))",
color: "white",
boxShadow: "0 2px 8px hsl(168 75% 32% / 0.25)",
boxShadow: "0 4px 14px hsl(168 75% 32% / 0.3)",
}}
>
Get Started
</Button>
<motion.div
className="absolute inset-0 z-0 bg-white/20"
initial={{ x: "-100%" }}
whileHover={{ x: "100%" }}
transition={{ duration: 0.6, ease: "easeInOut" }}
/>
<span className="relative z-10">Get Started</span>
</motion.button>
</Link>
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# First, let's check if the file exists and examine its content
fd "navbar.tsx" --type f

Repository: Ker102/ViperMesh

Length of output: 89


🏁 Script executed:

# Get context around lines 66-94 in the navbar component
cat -n components/landing/navbar.tsx | sed -n '60,100p'

Repository: Ker102/ViperMesh

Length of output: 1769


🌐 Web query:

Next.js Link component renders anchor tag button inside

💡 Result:

No, the Next.js Link component does not render an anchor tag containing a button element by default. The Link component renders as an <a> tag when its direct child is text or when explicitly structured that way, but its behavior depends on the children provided. Key points from official documentation: - Link "extends the HTML <a> element" and "renders to an <a> element" (App Router docs, Pages Router docs). - Example: <Link href="/dashboard">Dashboard</Link> outputs <a href="/dashboard">Dashboard</a>. - If the child is a non-string React element like <button>, Link returns the child directly without wrapping in <a> (confirmed in Next.js source code references and Stack Overflow analyses). Putting a <button> inside Link creates nested interactive elements (<a><button></a>), which is invalid HTML, harms accessibility (e.g., double focus on tab navigation), and causes unexpected behavior like both onClick and navigation firing. Solutions include: - Use e.preventDefault and e.stopPropagation on the button's onClick to block Link navigation. - Avoid nesting: Use Link for navigation semantics or router.push with prefetch for buttons. - For styled buttons that navigate, use libraries like shadcn/ui with asChild prop to compose properly without nesting. This applies to both App Router (Next.js 13+) and Pages Router, with App Router being current as of 2026.

Citations:


🌐 Web query:

HTML5 interactive content model button inside anchor tag accessibility

💡 Result:

No, placing a button element inside an anchor (<a>) tag is not valid under the HTML5 content model and poses significant accessibility issues. The HTML5 specification defines the <a> element's content model as transparent but explicitly prohibits interactive content descendants, including <button> elements. This is confirmed in the official W3C HTML Standard: "Content model: Transparent, but there must be no interactive content descendant." Button is classified as interactive content. Accessibility problems arise because nesting interactive elements confuses screen readers and assistive technologies. Screen readers may fail to announce the inner button properly, create empty tab stops, or misinterpret roles and names, violating WCAG 2.1/2.2 criteria such as 4.1.2 Name, Role, Value (Level A) and potentially 2.1.1 Keyboard (A). Sources describe this as creating focus problems, interference in event handling, and poor support across browsers and AT. Workarounds like preventDefault on clicks exist but do not resolve validity or full a11y compliance; they are discouraged. Instead, use semantic alternatives: style links as buttons (role="button") for navigation or buttons with JS navigation for actions, ensuring no nesting.

Citations:


Don't nest button elements inside Link.

Both CTAs render as <a><button /></a>, which violates the HTML5 content model—the <a> element must not contain interactive content descendants. This breaks keyboard navigation and screen-reader behavior, creating focus problems and WCAG 2.1 compliance violations (4.1.2 Name, Role, Value; 2.1.1 Keyboard). Render these as styled links instead, or put the Link inside a non-interactive motion wrapper.

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

In `@components/landing/navbar.tsx` around lines 66 - 94, The CTAs use
motion.button inside Link which creates nested interactive elements; change both
motion.button usages (the "Log in" and "Get Started" CTA blocks) to
non-interactive anchors by rendering motion.a (or using Link with an <a> child)
instead of motion.button, keep the same className, motion props
(whileHover/whileTap) and the inner motion.div for the animated sheen, and add
an appropriate aria-label if the text isn't sufficient; alternatively wrap Link
around a non-interactive motion.div and put the anchor inside, but do not nest a
<button> inside Link to resolve the accessibility/focus issues.

Comment on lines +146 to +208
// ── Collapsed summary when agent is done ──
if (!isActive && completedCount > 0) {
return (
<details
className="rounded-xl border px-4 py-3 mb-3 transition-all duration-300"
style={{
backgroundColor: "var(--forge-glass)",
borderColor: "hsl(var(--forge-border))",
backdropFilter: "blur(12px)",
boxShadow: "var(--forge-shadow)",
}}
>
<summary
className="cursor-pointer flex items-center gap-2 text-xs font-semibold uppercase tracking-wider select-none"
style={{ color: "hsl(var(--forge-text-subtle))" }}
>
<svg
width="14"
height="14"
viewBox="0 0 24 24"
fill="none"
stroke="hsl(150 60% 45%)"
strokeWidth="2.5"
strokeLinecap="round"
strokeLinejoin="round"
className="shrink-0"
>
<polyline points="20 6 9 17 4 12" />
</svg>
Done — {completedCount} tool{completedCount !== 1 ? "s" : ""} used
</summary>
<div className="mt-2 space-y-0.5">
{allCompleted.map((e, i) => (
<div
key={`${e.toolName}-${e.timestamp}-${i}`}
className="flex items-center gap-2 py-1 px-2 rounded-lg opacity-60"
>
<ToolIcon status="completed" />
<span
className="text-xs"
style={{ color: "hsl(var(--forge-text-muted))" }}
>
{e.label}
</span>
<svg
width="12"
height="12"
viewBox="0 0 24 24"
fill="none"
stroke="hsl(150 60% 45%)"
strokeWidth="3"
strokeLinecap="round"
strokeLinejoin="round"
className="ml-auto shrink-0"
>
<polyline points="20 6 9 17 4 12" />
</svg>
</div>
))}
</div>
</details>
)
}
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

Handle terminal failures separately from the “done” path.

When isActive is false and completedCount === 0, this falls through to the live panel and still renders the pulsing “Agent working” header. Mixed success/failure runs also collapse to a success-only summary, so failed tools disappear once streaming stops.

Suggested fix
+  const failedCount = useMemo(
+    () => toolEvents.filter((e) => e.status === "failed").length,
+    [toolEvents]
+  )
+
-  if (!isActive && completedCount > 0) {
+  if (!isActive && (completedCount > 0 || failedCount > 0)) {
     return (
       <details
         className="rounded-xl border px-4 py-3 mb-3 transition-all duration-300"
         style={{
           backgroundColor: "var(--forge-glass)",
@@
         <summary
           className="cursor-pointer flex items-center gap-2 text-xs font-semibold uppercase tracking-wider select-none"
           style={{ color: "hsl(var(--forge-text-subtle))" }}
         >
@@
-          Done — {completedCount} tool{completedCount !== 1 ? "s" : ""} used
+          {failedCount > 0
+            ? `Stopped — ${completedCount} completed, ${failedCount} failed`
+            : `Done — ${completedCount} tool${completedCount !== 1 ? "s" : ""} used`}
         </summary>
@@
-        <PulsingDot />
+        {isActive && <PulsingDot />}
         <span
           className="text-xs font-semibold uppercase tracking-wider"
           style={{ color: "hsl(var(--forge-text-subtle))" }}
         >
-          Agent working
+          {isActive ? "Agent working" : "Agent stopped"}
         </span>

Also applies to: 222-229

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

In `@components/projects/agent-activity.tsx` around lines 146 - 208, The collapsed
“Done” branch currently treats any terminal non-active state the same and hides
failures; update the conditional rendering around isActive so terminal failure
cases are handled separately: keep the existing Done summary only when !isActive
&& completedCount > 0, add a new branch for !isActive && completedCount === 0 &&
failedCount > 0 (or derive failed entries from allCompleted/allFailed) that
renders a failure summary and lists failed tools (use a different ToolIcon
status like "failed" and list from allFailed), and for mixed results when
!isActive && failedCount > 0 && completedCount > 0 render a combined summary
that shows both success and failure lists; ensure these branches short-circuit
so the live “Agent working” header never renders after terminal failure.

Comment on lines +1481 to +1493
{(() => {
// Derive whether a tool call is currently in-flight
const startedTools = new Set<string>()
for (const evt of agentEvents) {
if (evt.type === "agent:tool_call") {
const tc = evt as unknown as { toolName: string; status: string }
if (tc.status === "started") startedTools.add(tc.toolName)
else startedTools.delete(tc.toolName)
}
}
const hasActiveTool = startedTools.size > 0
const isThinking = agentActive && !hasActiveTool

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

Count active tool calls instead of toggling by tool name.

Lines 1483-1489 track in-flight work with a Set<string>. If two agent:tool_call events for the same tool overlap, the first completion removes the name and flips the UI back to “Thinking…” even though another call is still running.

Suggested fix
-              const startedTools = new Set<string>()
+              const activeToolCounts = new Map<string, number>()
               for (const evt of agentEvents) {
                 if (evt.type === "agent:tool_call") {
                   const tc = evt as unknown as { toolName: string; status: string }
-                  if (tc.status === "started") startedTools.add(tc.toolName)
-                  else startedTools.delete(tc.toolName)
+                  if (tc.status === "started") {
+                    activeToolCounts.set(tc.toolName, (activeToolCounts.get(tc.toolName) ?? 0) + 1)
+                  } else {
+                    const next = (activeToolCounts.get(tc.toolName) ?? 0) - 1
+                    if (next > 0) activeToolCounts.set(tc.toolName, next)
+                    else activeToolCounts.delete(tc.toolName)
+                  }
                 }
               }
-              const hasActiveTool = startedTools.size > 0
+              const hasActiveTool = activeToolCounts.size > 0
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/projects/project-chat.tsx` around lines 1481 - 1493, The current
logic builds a Set<string> (startedTools) from agentEvents and toggles presence
by tool name, which fails when multiple overlapping calls for the same tool
exist; replace the Set with a Map<string, number> (or a per-tool counter) and
iterate agentEvents to increment the count for a tool on a "started" event and
decrement on completion/failure/finished events, removing the entry when the
count reaches zero; then compute hasActiveTool by checking if any map value > 0
(or the total sum > 0) and keep isThinking = agentActive && !hasActiveTool,
using the existing agentEvents, startedTools (renamed), hasActiveTool and
isThinking identifiers to locate where to change the logic.

Comment on lines +1102 to +1128
// Detect short "retry" / follow-up messages that are too vague for RAG.
// If the last message is very short or matches retry patterns, fall back to
// the longest previous human message for a more meaningful search query.
const RETRY_PATTERNS = /^(try\s*again|redo|retry|again|re-?run|go\s*again|one\s*more|repeat|do\s*it\s*again)/i
const isShortFollowUp = lastContent.length < 30 || RETRY_PATTERNS.test(lastContent.trim())

let content = lastContent
if (isShortFollowUp) {
// Find the longest previous human message as a better RAG query
const allHumanMessages = messages.filter((m) => isHumanMessage(m))
let bestQuery = ""
for (const hm of allHumanMessages) {
const hmRec = hm as unknown as Record<string, unknown>
const hmContent = typeof hmRec.content === "string"
? hmRec.content
: Array.isArray(hmRec.content)
? (hmRec.content as Array<{ text?: string }>).map((c) => c.text ?? "").join("")
: ""
// Pick the longest human message that isn't itself a short follow-up
if (hmContent.length > bestQuery.length && hmContent.length > 30) {
bestQuery = hmContent
}
}
if (bestQuery) {
console.log(`[RAG] Follow-up detected ("${lastContent.slice(0, 40)}") — using original prompt for search (${bestQuery.length} chars)`)
content = bestQuery
}
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

Use the latest substantive prompt for retry fallback, not the longest one.

Line 1106 treats every short message as a retry, so legitimate edits like “add bevel” or “make it blue” lose their current intent. Then Lines 1111-1123 pick the longest prior human message, which can pull RAG context from an unrelated earlier task.

Suggested fix
-        const isShortFollowUp = lastContent.length < 30 || RETRY_PATTERNS.test(lastContent.trim())
+        const isExplicitRetry = RETRY_PATTERNS.test(lastContent.trim())
 
         let content = lastContent
-        if (isShortFollowUp) {
-          // Find the longest previous human message as a better RAG query
-          const allHumanMessages = messages.filter((m) => isHumanMessage(m))
+        if (isExplicitRetry) {
+          // Walk backward to the nearest substantive human prompt
+          const allHumanMessages = messages.filter((m) => isHumanMessage(m)).slice(0, -1).reverse()
           let bestQuery = ""
           for (const hm of allHumanMessages) {
             const hmRec = hm as unknown as Record<string, unknown>
             const hmContent = typeof hmRec.content === "string"
               ? hmRec.content
               : Array.isArray(hmRec.content)
                 ? (hmRec.content as Array<{ text?: string }>).map((c) => c.text ?? "").join("")
                 : ""
-            // Pick the longest human message that isn't itself a short follow-up
-            if (hmContent.length > bestQuery.length && hmContent.length > 30) {
+            if (hmContent.length > 30 && !RETRY_PATTERNS.test(hmContent.trim())) {
               bestQuery = hmContent
+              break
             }
           }
           if (bestQuery) {
-            content = bestQuery
+            content = `${bestQuery}\n\nFollow-up: ${lastContent}`
           }
         }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/ai/agents.ts` around lines 1102 - 1128, The current logic marks any short
lastContent as a retry and then picks the longest prior human message
(RETRY_PATTERNS, isShortFollowUp, allHumanMessages, bestQuery), which can pull
unrelated context; change isShortFollowUp so it only triggers for explicit retry
patterns or truly negligible edits (e.g. match RETRY_PATTERNS OR
lastContent.length < a very small threshold) rather than every short message,
and when isShortFollowUp is true, iterate messages in reverse (or reverse
allHumanMessages) to select the most recent prior human message whose content
length > 30 and that does not itself match RETRY_PATTERNS, then set content to
that recent substantive message instead of the longest one.

Comment on lines +13 to +17
5. **Visual Confirmation — MANDATORY**: You MUST call `get_viewport_screenshot` at these checkpoints:
- After creating the main geometry/objects (to verify placement, scale, and proportions)
- After setting up lighting (to verify the scene is properly illuminated)
- Before calling `render_image` (final visual check)
Skipping visual verification is a serious error — `get_viewport_screenshot` is your ONLY way to see the scene. The `render_image` tool does NOT return visual data you can analyze.
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

Keep the few-shot examples consistent with the new screenshot rule.

This block makes get_viewport_screenshot mandatory after lighting and before render_image, but the examples below still show flows that skip those checkpoints. That gives the model a counterexample inside the same prompt and weakens the new rule.

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

In `@lib/orchestration/prompts/blender-agent-system.md` around lines 13 - 17, The
few-shot examples in blender-agent-system.md contradict the "Visual Confirmation
— MANDATORY" rule; update each example sequence so every example calls
get_viewport_screenshot after creating main geometry/objects, after setting up
lighting, and immediately before calling render_image, removing any flows that
skip those checkpoints and ensuring the examples mirror the exact phrasing and
ordering of the rule (referencing get_viewport_screenshot and render_image) so
no counterexamples remain.

Comment on lines 21 to 30
bl_info = {
"name": "ModelForge Blender",
"author": "ModelForge Team",
"name": "ViperMesh Blender",
"author": "ViperMesh Team",
"version": (1, 1, 0),
"blender": (3, 0, 0),
"location": "View3D > Sidebar > ModelForge",
"description": "Connect Blender to ModelForge AI Assistant",
"location": "View3D > Sidebar > ViperMesh",
"description": "Connect Blender to ViperMesh AI Assistant",
"category": "Interface",
"doc_url": "https://github.com/Ker102/ModelForge",
}
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

Inconsistent doc_url still references old ModelForge repo.

The doc_url in bl_info still points to https://github.com/Ker102/ModelForge while the rest of the addon has been rebranded to ViperMesh. This should be updated for consistency.

📝 Suggested fix
-    "doc_url": "https://github.com/Ker102/ModelForge",
+    "doc_url": "https://github.com/Ker102/ViperMesh",
📝 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
bl_info = {
"name": "ModelForge Blender",
"author": "ModelForge Team",
"name": "ViperMesh Blender",
"author": "ViperMesh Team",
"version": (1, 1, 0),
"blender": (3, 0, 0),
"location": "View3D > Sidebar > ModelForge",
"description": "Connect Blender to ModelForge AI Assistant",
"location": "View3D > Sidebar > ViperMesh",
"description": "Connect Blender to ViperMesh AI Assistant",
"category": "Interface",
"doc_url": "https://github.com/Ker102/ModelForge",
}
bl_info = {
"name": "ViperMesh Blender",
"author": "ViperMesh Team",
"version": (1, 1, 0),
"blender": (3, 0, 0),
"location": "View3D > Sidebar > ViperMesh",
"description": "Connect Blender to ViperMesh AI Assistant",
"category": "Interface",
"doc_url": "https://github.com/Ker102/ViperMesh",
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@public/downloads/vipermesh-addon.py` around lines 21 - 30, Update the bl_info
dictionary entry doc_url in vipermesh-addon.py so it no longer points to the old
ModelForge repo; locate the bl_info symbol and change the "doc_url" value to the
correct ViperMesh documentation or repo URL (e.g., the ViperMesh GitHub or docs
page) to keep branding consistent.

return {"error": f"Invalid asset type: {asset_type}. Must be one of: hdris, textures, models, all"}

response = requests.get(f"https://api.polyhaven.com/categories/{asset_type}", headers=REQ_HEADERS, timeout=30)
response = requests.get(f"https://api.polyhaven.com/categories/{asset_type}", headers=REQ_HEADERS)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Missing timeout on HTTP request can hang Blender indefinitely.

This requests.get() call (and several others flagged by static analysis at lines 1460, 1480, 1604, 1741, 1759) lacks a timeout parameter. If the Poly Haven API is slow or unresponsive, Blender will freeze with no way to recover.

🐛 Suggested fix for all affected lines
 def get_polyhaven_categories(self, asset_type):
     ...
-    response = requests.get(f"https://api.polyhaven.com/categories/{asset_type}", headers=REQ_HEADERS)
+    response = requests.get(f"https://api.polyhaven.com/categories/{asset_type}", headers=REQ_HEADERS, timeout=30)

Apply the same fix to:

  • Line 1460: search_polyhaven_assets
  • Line 1480: download_polyhaven_asset (files endpoint)
  • Line 1604: texture download
  • Line 1741: model download
  • Line 1759: include file download

A 30-second timeout is reasonable for API calls; consider 60 seconds for file downloads.

📝 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
response = requests.get(f"https://api.polyhaven.com/categories/{asset_type}", headers=REQ_HEADERS)
response = requests.get(f"https://api.polyhaven.com/categories/{asset_type}", headers=REQ_HEADERS, timeout=30)
🧰 Tools
🪛 Ruff (0.15.6)

[error] 1438-1438: Probable use of requests call without timeout

(S113)

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

In `@public/downloads/vipermesh-addon.py` at line 1438, The requests.get() calls
to the Poly Haven API currently have no timeout and can hang Blender; update
each call to include an appropriate timeout: add timeout=30 for lightweight API
endpoints (e.g., the call in response =
requests.get(f"https://api.polyhaven.com/categories/{asset_type}", ...) and the
functions search_polyhaven_assets and related API queries) and use timeout=60
for larger file downloads (e.g., download_polyhaven_asset, the texture download,
model download, and include file download functions) so network requests fail
fast and don’t block the UI.

Comment on lines +52 to 58
### 🌐 Premium Web Dashboard UI
- **Dual Modes**:
- **Autopilot**: Conversational AI assistant for procedural generation.
- **Studio**: Full-page, immersive workspace with an icon-sidebar and interactive tool-card grid.
- **Teal Design System**: Professional, high-end visual aesthetics (#0d9488 focus) with interactive 3D hero sections and animated SVGs.
- **Supabase Auth**: Secure authentication with Google and GitHub OAuth.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Add blank lines around the new headings.

markdownlint is already flagging MD022 on these sections. The new headings run directly into the following paragraph/list/rule, so the README stays lint-failing until blank lines are added before and after them.

Also applies to: 66-76, 94-99

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

In `@README.md` around lines 52 - 58, The new markdown headings (e.g., "### 🌐
Premium Web Dashboard UI") lack surrounding blank lines causing markdownlint
MD022; add a single blank line before and after each heading and the same for
the other flagged headings mentioned (lines containing "Dual Modes", "Teal
Design System", "Supabase Auth" and the sections at 66-76 and 94-99) so each
heading is separated from preceding and following content and the MD022
violations are resolved.

Batch 1 (Quick Wins):
- Fix doc_url in both addon copies (ModelForge -> ViperMesh)
- Add timeout=30/60 to all requests.get() calls in addons
- Replace native img with Next.js Image in start-oauth page
- Fix markdown lint (MD040) in spatial-positioning-guide and test-prompts
- Add 'attach image' instruction to Test 18

Batch 2 (Component Fixes):
- Fix nested button inside Link a11y issue in navbar.tsx
- Add file input reset, 10MB validation, FileReader error handling
- Add failed tool count/display in agent-activity collapsed summary

Batch 3 (Logic Fixes):
- Fix RAG retry to pick most-recent substantive prompt (not longest)
- Fix stale closure via workflowStepsRef in studio-layout.tsx
- Add 'skipped' to StepCommandResult status union type
@Ker102 Ker102 merged commit 3fca5c2 into main Mar 24, 2026
5 of 6 checks passed
@coderabbitai coderabbitai bot added the scripts label Mar 24, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant