feat: add atomeam-stack unified dashboard - #39
Conversation
📝 WalkthroughWalkthroughAdds a new unified-app: project metadata and .gitignore, an ES-module Express server with read-only endpoints (status, repos, tools, logs, optional Notion-backed /api/notion/logs), static public SPA, and a dark-themed frontend that polls APIs every 10 seconds. ChangesUnified App Dashboard
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 4
🧹 Nitpick comments (1)
unified-app/package.json (1)
2-4: ⚡ Quick winMark the app as private to avoid accidental publication.
This repo looks like an internal app, so setting
"private": trueis a safer default.Suggested patch
{ "name": "atomeam-stack", + "private": true, "version": "1.0.0",🤖 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 `@unified-app/package.json` around lines 2 - 4, Set the npm package to private by adding the "private": true field to package.json so the internal app ("name": "atomeam-stack", "version": "1.0.0", "description": "Unified dashboard for atomeam") cannot be accidentally published; insert the "private": true property at the top-level of package.json alongside the existing name/version/description entries.
🤖 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 @.gitignore:
- Around line 1-3: Add patterns to .gitignore to exclude dotenv files so secrets
aren't committed; update the existing .gitignore (which already contains
node_modules, .DS_Store, *.log) to include entries for .env and any
environment-file variants such as .env.* (e.g., ".env" and ".env.*") so all
dotenv files are ignored across environments and local overrides.
In `@unified-app/public/index.html`:
- Around line 46-64: The loadData function should guard against fetch failures
and run the three API calls in parallel: change the sequential fetches
(fetch('/api/status'), fetch('/api/repos'), fetch('/api/tools')) to run
concurrently (using Promise.all or Promise.allSettled) and update the DOM only
from successful responses; wrap the whole loadData body in a try/catch so any
thrown error is caught, log the error and update UI with a safe fallback (e.g.,
empty values or an error message) without allowing an unhandled rejection to
stop future polling, and ensure the polling mechanism (setInterval or a
self-scheduling setTimeout inside loadData) continues regardless of individual
request failures.
In `@unified-app/server.js`:
- Around line 42-43: The route handler for app.get('/api/repos') (and the
similar handler around lines 50-51) returns exists: true for every item; change
it to compute the real existence by checking the filesystem or the authoritative
source for each repo/resource. Update the handler(s) to be async, use
fs.promises.access or fs.existsSync (or your configured repo-check function) to
determine existence for each CONFIG.repos entry, build the response objects with
the actual boolean in the exists field, and await Promise.all when mapping async
checks so the response contains real existence states for both endpoints.
- Line 11: The app is enabling unrestricted CORS via the current app.use(cors())
call; replace that call so CORS is restricted to same-origin frontend requests
by configuring the cors middleware with origin set to "http://localhost:3000"
and methods limited to ["GET"] (i.e., update the app.use(cors()) invocation in
server.js to pass the options object restricting origin and methods).
---
Nitpick comments:
In `@unified-app/package.json`:
- Around line 2-4: Set the npm package to private by adding the "private": true
field to package.json so the internal app ("name": "atomeam-stack", "version":
"1.0.0", "description": "Unified dashboard for atomeam") cannot be accidentally
published; insert the "private": true property at the top-level of package.json
alongside the existing name/version/description entries.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: 17f1230c-f762-45e3-9995-3c3230190cc5
📒 Files selected for processing (4)
.gitignoreunified-app/package.jsonunified-app/public/index.htmlunified-app/server.js
| node_modules/ | ||
| .DS_Store | ||
| *.log |
There was a problem hiding this comment.
Ignore dotenv files to prevent accidental secret commits.
.env files are currently not ignored, so local credentials can be committed by mistake.
Suggested patch
node_modules/
.DS_Store
*.log
+.env
+.env.*
+!.env.example📝 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.
| node_modules/ | |
| .DS_Store | |
| *.log | |
| node_modules/ | |
| .DS_Store | |
| *.log | |
| .env | |
| .env.* | |
| !.env.example |
🤖 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 @.gitignore around lines 1 - 3, Add patterns to .gitignore to exclude dotenv
files so secrets aren't committed; update the existing .gitignore (which already
contains node_modules, .DS_Store, *.log) to include entries for .env and any
environment-file variants such as .env.* (e.g., ".env" and ".env.*") so all
dotenv files are ignored across environments and local overrides.
| async function loadData() { | ||
| const status = await fetch('/api/status').then(r => r.json()); | ||
| document.getElementById('timestamp').textContent = new Date(status.timestamp).toLocaleString(); | ||
| document.getElementById('status').innerHTML = ` | ||
| <div class="stat"><span class="stat-name">Repos</span><span class="stat-value">${status.summary.totalRepos}</span></div> | ||
| <div class="stat"><span class="stat-name">Logs</span><span class="stat-value">${status.summary.totalLogs}</span></div> | ||
| <div class="stat"><span class="stat-name">Tools</span><span class="stat-value">${status.summary.totalTools}</span></div> | ||
| `; | ||
| const repos = await fetch('/api/repos').then(r => r.json()); | ||
| document.getElementById('repos').innerHTML = repos.map(r => ` | ||
| <div class="stat"><span class="stat-name">${r.name}</span><span class="stat-value status-active">${r.exists ? '✓' : '✗'}</span></div> | ||
| `).join(''); | ||
| const tools = await fetch('/api/tools').then(r => r.json()); | ||
| document.getElementById('tools').innerHTML = tools.map(t => ` | ||
| <div class="stat"><span class="stat-name">${t.name}</span><span class="stat-value">${t.cmd}</span></div> | ||
| `).join(''); | ||
| } | ||
| loadData(); | ||
| setInterval(loadData, 10000); |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Verify presence of error handling around loadData and parallel fetch usage.
rg -n "async function loadData|try\\s*\\{|catch\\s*\\(|Promise\\.all\\(" unified-app/public/index.htmlRepository: atomeam/atomarcade-bridge
Length of output: 104
Handle fetch failures to prevent unhandled rejections and broken polling.
loadData lacks error handling, so any API failure will throw an unhandled rejection and break the 10-second refresh cycle. Additionally, the three fetch calls are sequential when they could execute in parallel.
Suggested patch
async function loadData() {
- const status = await fetch('/api/status').then(r => r.json());
- document.getElementById('timestamp').textContent = new Date(status.timestamp).toLocaleString();
- document.getElementById('status').innerHTML = `
- <div class="stat"><span class="stat-name">Repos</span><span class="stat-value">${status.summary.totalRepos}</span></div>
- <div class="stat"><span class="stat-name">Logs</span><span class="stat-value">${status.summary.totalLogs}</span></div>
- <div class="stat"><span class="stat-name">Tools</span><span class="stat-value">${status.summary.totalTools}</span></div>
- `;
- const repos = await fetch('/api/repos').then(r => r.json());
- document.getElementById('repos').innerHTML = repos.map(r => `
- <div class="stat"><span class="stat-name">${r.name}</span><span class="stat-value status-active">${r.exists ? '✓' : '✗'}</span></div>
- `).join('');
- const tools = await fetch('/api/tools').then(r => r.json());
- document.getElementById('tools').innerHTML = tools.map(t => `
- <div class="stat"><span class="stat-name">${t.name}</span><span class="stat-value">${t.cmd}</span></div>
- `).join('');
+ try {
+ const [status, repos, tools] = await Promise.all([
+ fetch('/api/status').then(r => r.json()),
+ fetch('/api/repos').then(r => r.json()),
+ fetch('/api/tools').then(r => r.json())
+ ]);
+ document.getElementById('timestamp').textContent = new Date(status.timestamp).toLocaleString();
+ document.getElementById('status').innerHTML = `
+ <div class="stat"><span class="stat-name">Repos</span><span class="stat-value">${status.summary.totalRepos}</span></div>
+ <div class="stat"><span class="stat-name">Logs</span><span class="stat-value">${status.summary.totalLogs}</span></div>
+ <div class="stat"><span class="stat-name">Tools</span><span class="stat-value">${status.summary.totalTools}</span></div>
+ `;
+ document.getElementById('repos').innerHTML = repos.map(r => `
+ <div class="stat"><span class="stat-name">${r.name}</span><span class="stat-value status-active">${r.exists ? '✓' : '✗'}</span></div>
+ `).join('');
+ document.getElementById('tools').innerHTML = tools.map(t => `
+ <div class="stat"><span class="stat-name">${t.name}</span><span class="stat-value">${t.cmd}</span></div>
+ `).join('');
+ } catch (err) {
+ document.getElementById('status').textContent = 'Failed to load dashboard data.';
+ }
}🤖 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 `@unified-app/public/index.html` around lines 46 - 64, The loadData function
should guard against fetch failures and run the three API calls in parallel:
change the sequential fetches (fetch('/api/status'), fetch('/api/repos'),
fetch('/api/tools')) to run concurrently (using Promise.all or
Promise.allSettled) and update the DOM only from successful responses; wrap the
whole loadData body in a try/catch so any thrown error is caught, log the error
and update UI with a safe fallback (e.g., empty values or an error message)
without allowing an unhandled rejection to stop future polling, and ensure the
polling mechanism (setInterval or a self-scheduling setTimeout inside loadData)
continues regardless of individual request failures.
| const app = express(); | ||
| const PORT = 3000; | ||
|
|
||
| app.use(cors()); |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Verify whether frontend uses cross-origin API calls and whether open CORS is present.
rg -n "fetch\(['\"]https?://" unified-app/public/index.html
rg -n "app.use\\(cors\\(" unified-app/server.jsRepository: atomeam/atomarcade-bridge
Length of output: 89
🏁 Script executed:
# Search for all API calls in the frontend, not just cross-origin ones
rg -n "fetch\|XMLHttpRequest\|api/repos\|api/logs" unified-app/public/ -t js -t htmlRepository: atomeam/atomarcade-bridge
Length of output: 51
🏁 Script executed:
# Check the server.js file to understand the full API setup
cat -n unified-app/server.js | head -50Repository: atomeam/atomarcade-bridge
Length of output: 1790
🏁 Script executed:
# Check if there's any frontend JavaScript code outside of index.html
find unified-app/public -type f -name "*.js" -o -name "*.html" | head -20Repository: atomeam/atomarcade-bridge
Length of output: 99
🏁 Script executed:
# Read the frontend HTML to see how it calls APIs
cat -n unified-app/public/index.htmlRepository: atomeam/atomarcade-bridge
Length of output: 3665
Restrict CORS to same-origin requests only.
The frontend makes only same-origin API calls (relative paths like /api/status, /api/repos, /api/tools), so unrestricted CORS is unnecessary and weakens security. Restrict it to localhost:3000 and GET methods only.
Suggested patch
-app.use(cors());
+app.use(cors({
+ origin: ['http://localhost:3000'],
+ methods: ['GET']
+}));📝 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.
| app.use(cors()); | |
| app.use(cors({ | |
| origin: ['http://localhost:3000'], | |
| methods: ['GET'] | |
| })); |
🤖 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 `@unified-app/server.js` at line 11, The app is enabling unrestricted CORS via
the current app.use(cors()) call; replace that call so CORS is restricted to
same-origin frontend requests by configuring the cors middleware with origin set
to "http://localhost:3000" and methods limited to ["GET"] (i.e., update the
app.use(cors()) invocation in server.js to pass the options object restricting
origin and methods).
| app.get('/api/repos', (req, res) => { | ||
| res.json(CONFIG.repos.map(r => ({ name: r.name, exists: true }))); |
There was a problem hiding this comment.
Return real existence state instead of hardcoded true.
Lines 43 and 51 always report resources as existing, so dashboard health is misleading.
Suggested patch
-import { readFileSync } from 'fs';
-import { join } from 'path';
+import { existsSync } from 'fs';
+import { join, resolve } from 'path';
@@
app.get('/api/repos', (req, res) => {
- res.json(CONFIG.repos.map(r => ({ name: r.name, exists: true })));
+ res.json(
+ CONFIG.repos.map(r => ({
+ name: r.name,
+ exists: existsSync(resolve(__dirname, r.path))
+ }))
+ );
});
@@
app.get('/api/logs', (req, res) => {
- res.json(CONFIG.logs.map(l => ({ name: l.name, exists: true })));
+ res.json(
+ CONFIG.logs.map(l => ({
+ name: l.name,
+ exists: existsSync(resolve(__dirname, l.path))
+ }))
+ );
});Also applies to: 50-51
🤖 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 `@unified-app/server.js` around lines 42 - 43, The route handler for
app.get('/api/repos') (and the similar handler around lines 50-51) returns
exists: true for every item; change it to compute the real existence by checking
the filesystem or the authoritative source for each repo/resource. Update the
handler(s) to be async, use fs.promises.access or fs.existsSync (or your
configured repo-check function) to determine existence for each CONFIG.repos
entry, build the response objects with the actual boolean in the exists field,
and await Promise.all when mapping async checks so the response contains real
existence states for both endpoints.
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 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 `@unified-app/server.js`:
- Around line 15-21: The code incorrectly falls back to
process.env.GEMINI_API_KEY when building NOTION_KEY causing false "connected"
state; update the NOTION_KEY assignment to use only process.env.NOTION_API_KEY,
remove any use of process.env.GEMINI_API_KEY in this file, and ensure the Notion
client is only instantiated when NOTION_KEY is truthy (affecting the notion
variable/Client creation and any connection/status checks like the status
endpoint that reads notion). Locate the NOTION_KEY constant, the notion = new
Client({ auth: NOTION_KEY }) instantiation, and any checks that infer connection
status to make this change.
- Around line 68-86: The /api/notion/logs route currently returns 200 on errors
and exposes raw upstream errors; update the handler
(app.get('/api/notion/logs')) so that if notion is not configured (the notion
variable) it responds with res.status(503).json({ error: 'Notion service
unavailable', logs: [] }); and in the catch block for the notion.databases.query
call respond with res.status(502).json({ error: 'Failed to fetch Notion logs',
logs: [] }); while logging the real error server-side (e.g., console.error or
existing logger) instead of returning e.message to the client; keep the
successful response unchanged (res.status(200).json({ logs })).
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: 886b13c3-177f-4aac-b0e5-91df645bfac6
⛔ Files ignored due to path filters (1)
unified-app/package-lock.jsonis excluded by!**/package-lock.json
📒 Files selected for processing (2)
unified-app/package.jsonunified-app/server.js
🚧 Files skipped from review as they are similar to previous changes (1)
- unified-app/package.json
| const NOTION_KEY = process.env.NOTION_API_KEY || process.env.GEMINI_API_KEY; | ||
| const LOGS_DB_ID = '4ee3980e-62fa-4abe-a716-c7d6656011ba'; | ||
|
|
||
| let notion; | ||
| if (NOTION_KEY) { | ||
| notion = new Client({ auth: NOTION_KEY }); | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
find . -name "server.js" -type f | head -20Repository: atomeam/atomarcade-bridge
Length of output: 93
🏁 Script executed:
cat -n ./unified-app/server.js | head -60Repository: atomeam/atomarcade-bridge
Length of output: 2095
🏁 Script executed:
cat -n ./unified-app/server.js | grep -A 5 -B 5 "notion\."Repository: atomeam/atomarcade-bridge
Length of output: 519
🏁 Script executed:
grep -n "GEMINI_API_KEY" ./unified-app/server.jsRepository: atomeam/atomarcade-bridge
Length of output: 149
🏁 Script executed:
wc -l ./unified-app/server.js && grep -c "process.env" ./unified-app/server.jsRepository: atomeam/atomarcade-bridge
Length of output: 98
Remove fallback to GEMINI_API_KEY for Notion authentication.
Using an unrelated API key causes a false "connected" state. The status endpoint reports connected: true (line 49), but subsequent Notion API calls at line 73 fail with invalid credentials. Keep Notion authentication bound to NOTION_API_KEY only.
Suggested patch
-const NOTION_KEY = process.env.NOTION_API_KEY || process.env.GEMINI_API_KEY;
+const NOTION_KEY = process.env.NOTION_API_KEY;Also applies to: 48-50
🤖 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 `@unified-app/server.js` around lines 15 - 21, The code incorrectly falls back
to process.env.GEMINI_API_KEY when building NOTION_KEY causing false "connected"
state; update the NOTION_KEY assignment to use only process.env.NOTION_API_KEY,
remove any use of process.env.GEMINI_API_KEY in this file, and ensure the Notion
client is only instantiated when NOTION_KEY is truthy (affecting the notion
variable/Client creation and any connection/status checks like the status
endpoint that reads notion). Locate the NOTION_KEY constant, the notion = new
Client({ auth: NOTION_KEY }) instantiation, and any checks that infer connection
status to make this change.
| app.get('/api/notion/logs', async (req, res) => { | ||
| if (!notion) { | ||
| return res.json({ error: 'No Notion API key', logs: [] }); | ||
| } | ||
| try { | ||
| const response = await notion.databases.query({ | ||
| database_id: LOGS_DB_ID, | ||
| sorts: [{ timestamp: 'created_time', direction: 'descending' }], | ||
| page_size: 10 | ||
| }); | ||
| const logs = response.results.map(page => ({ | ||
| id: page.id, | ||
| created: page.created_time, | ||
| properties: page.properties | ||
| })); | ||
| res.json({ logs }); | ||
| } catch (e) { | ||
| res.json({ error: e.message, logs: [] }); | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
find . -name "server.js" -o -name "servers.js" | head -20Repository: atomeam/atomarcade-bridge
Length of output: 93
🏁 Script executed:
git ls-files | grep -i server | head -20Repository: atomeam/atomarcade-bridge
Length of output: 91
🏁 Script executed:
sed -n '68,86p' unified-app/server.jsRepository: atomeam/atomarcade-bridge
Length of output: 628
🏁 Script executed:
wc -l unified-app/server.jsRepository: atomeam/atomarcade-bridge
Length of output: 94
🏁 Script executed:
grep -n "res.json\|res.status" unified-app/server.js | head -30Repository: atomeam/atomarcade-bridge
Length of output: 399
🏁 Script executed:
cat -n unified-app/server.jsRepository: atomeam/atomarcade-bridge
Length of output: 3178
Return proper HTTP error codes and avoid leaking raw upstream errors.
Lines 70 and 85 respond with HTTP 200 even on failures. Missing Notion configuration should return 503, and Notion API exceptions should return 502 with a sanitized error message (raw e.message leaks internal details to clients).
Suggested patch
app.get('/api/notion/logs', async (req, res) => {
if (!notion) {
- return res.json({ error: 'No Notion API key', logs: [] });
+ return res.status(503).json({ error: 'Notion integration is not configured', logs: [] });
}
try {
const response = await notion.databases.query({
database_id: LOGS_DB_ID,
sorts: [{ timestamp: 'created_time', direction: 'descending' }],
page_size: 10
});
const logs = response.results.map(page => ({
id: page.id,
created: page.created_time,
properties: page.properties
}));
res.json({ logs });
} catch (e) {
- res.json({ error: e.message, logs: [] });
+ console.error('Failed to fetch Notion logs:', e);
+ res.status(502).json({ error: 'Failed to fetch Notion logs', logs: [] });
}
});🤖 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 `@unified-app/server.js` around lines 68 - 86, The /api/notion/logs route
currently returns 200 on errors and exposes raw upstream errors; update the
handler (app.get('/api/notion/logs')) so that if notion is not configured (the
notion variable) it responds with res.status(503).json({ error: 'Notion service
unavailable', logs: [] }); and in the catch block for the notion.databases.query
call respond with res.status(502).json({ error: 'Failed to fetch Notion logs',
logs: [] }); while logging the real error server-side (e.g., console.error or
existing logger) instead of returning e.message to the client; keep the
successful response unchanged (res.status(200).json({ logs })).
Summary
unified-app/- a simple Node.js Express dashboard/api/status,/api/repos,/api/tools,/api/logsTo Run
@atomeam can click here to continue refining the PR
Summary by CodeRabbit
New Features
Chores