-
Notifications
You must be signed in to change notification settings - Fork 1
fix: restore UI resizing and filtering + add cross-platform QA gate #24
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,71 @@ | ||
| name: QA Matrix | ||
|
|
||
| on: | ||
| pull_request: | ||
| branches: ['main'] | ||
| push: | ||
| branches: ['main'] | ||
| workflow_dispatch: | ||
|
|
||
| permissions: | ||
| contents: read | ||
|
|
||
| jobs: | ||
| qa: | ||
| name: QA (${{ matrix.os }}) | ||
| runs-on: ${{ matrix.os }} | ||
| strategy: | ||
| fail-fast: false | ||
| matrix: | ||
| os: [ubuntu-latest, windows-latest, macos-latest] | ||
|
|
||
| steps: | ||
| - name: Checkout repository | ||
| uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd | ||
| with: | ||
| persist-credentials: false | ||
|
|
||
| - name: Setup Node.js | ||
| uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 | ||
| with: | ||
| node-version: 20 | ||
| package-manager-cache: false | ||
|
|
||
| - name: Install dependencies | ||
| run: npm ci | ||
|
|
||
| - name: Lint | ||
| run: npm run lint | ||
|
|
||
| - name: Unit and integration tests | ||
| run: npm test -- --runInBand | ||
|
|
||
| - name: Build TypeScript | ||
| run: npm run build:ts | ||
|
|
||
| - name: Build CSS | ||
| run: npm run build:css | ||
|
|
||
| - name: Build renderer bundle | ||
| run: npm run build:webpack | ||
|
|
||
| - name: Install Playwright browser (Linux) | ||
| if: runner.os == 'Linux' | ||
| run: npx playwright install --with-deps chromium | ||
|
|
||
| - name: Install Playwright browser (Windows/macOS) | ||
| if: runner.os != 'Linux' | ||
| run: npx playwright install chromium | ||
|
|
||
| - name: Capture UI screenshot | ||
| run: npm run qa:screenshot | ||
| env: | ||
| UI_SCREENSHOT_NAME: ui-${{ runner.os }}.png | ||
|
|
||
| - name: Upload UI screenshot | ||
| uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f | ||
| with: | ||
| name: ui-screenshot-${{ runner.os }} | ||
| path: dist/qa/screenshots/*.png | ||
| if-no-files-found: error | ||
| retention-days: 14 |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -0,0 +1,91 @@ | ||||||
| #!/usr/bin/env node | ||||||
|
|
||||||
| const fs = require('fs'); | ||||||
| const http = require('http'); | ||||||
| const path = require('path'); | ||||||
| const { chromium } = require('playwright'); | ||||||
|
|
||||||
| const ROOT_DIR = path.join(__dirname, '..'); | ||||||
| const RENDERER_DIR = path.join(ROOT_DIR, 'src', 'renderer'); | ||||||
| const SCREENSHOT_DIR = path.join(ROOT_DIR, 'dist', 'qa', 'screenshots'); | ||||||
| const SCREENSHOT_NAME = | ||||||
| process.env.UI_SCREENSHOT_NAME || `ui-${process.platform}-${process.arch}.png`; | ||||||
| const SCREENSHOT_PATH = path.join(SCREENSHOT_DIR, SCREENSHOT_NAME); | ||||||
| const PORT = Number(process.env.UI_SCREENSHOT_PORT || 4173); | ||||||
|
Comment on lines
+11
to
+14
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 1. ui_screenshot_name path not sanitized • SCREENSHOT_NAME is taken directly from process.env.UI_SCREENSHOT_NAME and used in path.join(SCREENSHOT_DIR, SCREENSHOT_NAME), allowing path traversal (e.g., ../...) and unintended writes outside dist/qa/screenshots. • This violates the requirement to validate/sanitize external inputs (environment variables) and can create a security risk in CI runners or other environments where env vars can be influenced. Agent Prompt
|
||||||
|
|
||||||
| const MIME_TYPES = { | ||||||
| '.css': 'text/css; charset=UTF-8', | ||||||
| '.html': 'text/html; charset=UTF-8', | ||||||
| '.js': 'application/javascript; charset=UTF-8', | ||||||
| '.json': 'application/json; charset=UTF-8', | ||||||
| '.png': 'image/png', | ||||||
| '.svg': 'image/svg+xml', | ||||||
| }; | ||||||
|
|
||||||
| function resolveFilePath(requestUrl) { | ||||||
| const urlPath = decodeURIComponent(requestUrl.split('?')[0]); | ||||||
| const relativePath = urlPath === '/' ? 'index.html' : urlPath.replace(/^\/+/, ''); | ||||||
| const absolutePath = path.resolve(RENDERER_DIR, relativePath); | ||||||
| const relativeToRoot = path.relative(RENDERER_DIR, absolutePath); | ||||||
|
|
||||||
| if (relativeToRoot.startsWith('..') || path.isAbsolute(relativeToRoot)) { | ||||||
| return null; | ||||||
| } | ||||||
|
|
||||||
| return absolutePath; | ||||||
| } | ||||||
|
|
||||||
| function createStaticServer() { | ||||||
| return http.createServer((request, response) => { | ||||||
| const requestedPath = resolveFilePath(request.url || '/'); | ||||||
|
|
||||||
| if (!requestedPath) { | ||||||
| response.writeHead(403, { 'Content-Type': 'text/plain; charset=UTF-8' }); | ||||||
| response.end('Forbidden'); | ||||||
| return; | ||||||
| } | ||||||
|
|
||||||
| fs.readFile(requestedPath, (error, content) => { | ||||||
| if (error) { | ||||||
| response.writeHead(404, { 'Content-Type': 'text/plain; charset=UTF-8' }); | ||||||
| response.end('Not Found'); | ||||||
| return; | ||||||
| } | ||||||
|
|
||||||
| const extension = path.extname(requestedPath).toLowerCase(); | ||||||
| const contentType = MIME_TYPES[extension] || 'application/octet-stream'; | ||||||
| response.writeHead(200, { 'Content-Type': contentType }); | ||||||
| response.end(content); | ||||||
| }); | ||||||
| }); | ||||||
| } | ||||||
|
|
||||||
| async function captureScreenshot() { | ||||||
| fs.mkdirSync(SCREENSHOT_DIR, { recursive: true }); | ||||||
|
|
||||||
| const server = createStaticServer(); | ||||||
| await new Promise((resolve, reject) => { | ||||||
| server.once('error', reject); | ||||||
| server.listen(PORT, '127.0.0.1', () => resolve()); | ||||||
| }); | ||||||
|
|
||||||
| const browser = await chromium.launch({ headless: true }); | ||||||
| const page = await browser.newPage({ viewport: { width: 1440, height: 900 } }); | ||||||
|
|
||||||
| try { | ||||||
| await page.goto(`http://127.0.0.1:${PORT}/index.html`, { waitUntil: 'networkidle' }); | ||||||
| await page.waitForSelector('#app', { timeout: 10000 }); | ||||||
| await page.waitForTimeout(1000); | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Using a fixed A better practice is to wait for a specific element to be visible, which ensures the UI is ready before capturing the screenshot. This makes the test more robust and efficient.
Suggested change
|
||||||
| await page.screenshot({ path: SCREENSHOT_PATH, fullPage: true }); | ||||||
| console.log(`UI screenshot captured: ${SCREENSHOT_PATH}`); | ||||||
| } finally { | ||||||
| await page.close(); | ||||||
| await browser.close(); | ||||||
| await new Promise((resolve) => server.close(resolve)); | ||||||
| } | ||||||
| } | ||||||
|
|
||||||
| captureScreenshot().catch((error) => { | ||||||
| console.error(`Failed to capture UI screenshot: ${error.message}`); | ||||||
| process.exit(1); | ||||||
| }); | ||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -272,7 +272,7 @@ const FileTreeComponent = ({ | |
| }; | ||
|
|
||
| return ( | ||
| <div className='file-tree rounded-md border border-gray-200 dark:border-gray-700'> | ||
| <div className='file-tree flex min-h-0 flex-1 flex-col rounded-md border border-gray-200 dark:border-gray-700'> | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: # Find FileTree usage in SourceTab and check the structure
rg -n 'FileTree' --type=tsx -B3 -A3 src/renderer/components/SourceTab.tsxRepository: codingworkflow/ai-code-fusion Length of output: 100 🏁 Script executed: # Check the specific area in FileTree.tsx around line 275
sed -n '270,280p' src/renderer/components/FileTree.tsxRepository: codingworkflow/ai-code-fusion Length of output: 621 🏁 Script executed: # Check SourceTab.tsx around line 395
sed -n '390,400p' src/renderer/components/SourceTab.tsxRepository: codingworkflow/ai-code-fusion Length of output: 436 Remove duplicate border styling between FileTree and parent container. The Remove the border classes from one of the two elements—either from the 🤖 Prompt for AI Agents |
||
| <div className='flex items-center justify-between border-b border-gray-200 dark:border-gray-700 bg-gray-50 dark:bg-gray-800 p-2'> | ||
| <div className='flex items-center'> | ||
| <input | ||
|
|
@@ -297,7 +297,7 @@ const FileTreeComponent = ({ | |
| </span> | ||
| </div> | ||
|
|
||
| <div className='max-h-96 overflow-auto p-2'> | ||
| <div className='flex-1 min-h-0 overflow-auto p-2'> | ||
| {items.length === 0 ? ( | ||
| <div className='flex flex-col items-center justify-center p-8 text-center text-gray-500 dark:text-gray-400'> | ||
| <svg | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -197,7 +197,7 @@ const ProcessedTab = ({ processedResult, onSave, onRefresh }: ProcessedTabProps) | |
| </div> | ||
| <div | ||
| id='processed-content' | ||
| className='max-h-96 overflow-auto rounded-md border border-gray-300 dark:border-gray-700 bg-white dark:bg-gray-700 p-4 shadow-sm' | ||
| className='max-h-[45vh] min-h-48 overflow-auto rounded-md border border-gray-300 dark:border-gray-700 bg-white dark:bg-gray-700 p-4 shadow-sm' | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Using viewport height units ( To align with the other responsive improvements in this PR, I recommend refactoring this component to use a flex column layout. This would make the "Processed Content" and "Files by Token Count" sections properly share the available space. Here's an example of how you could restructure the component: // Make the root div a flex container that fills the height
<div className='flex h-full min-h-0 flex-col'>
{/* ...action buttons... */}
{/* Make content sections flexible */}
<div className='mb-4 flex flex-1 flex-col min-h-0'>
{/* ...label... */}
<div id='processed-content' className='flex-1 min-h-0 overflow-auto ...'>
{/* ...pre... */}
</div>
</div>
<div className='mt-6 flex flex-1 flex-col min-h-0'>
{/* ...h3... */}
<div className='flex-1 min-h-0 overflow-y-auto ...'>
{/* ...table... */}
</div>
</div>
</div> |
||
| > | ||
| <pre className='whitespace-pre-wrap font-mono text-xs leading-5 dark:text-white'> | ||
| {processedResult.content} | ||
|
|
@@ -211,7 +211,7 @@ const ProcessedTab = ({ processedResult, onSave, onRefresh }: ProcessedTabProps) | |
| Files by Token Count | ||
| </h3> | ||
| <div className='rounded-md border border-gray-200 dark:border-gray-700 shadow-sm'> | ||
| <div className='h-60 max-h-60 overflow-y-auto overflow-x-hidden'> | ||
| <div className='h-[35vh] min-h-48 max-h-[45vh] overflow-y-auto overflow-x-hidden'> | ||
| <table className='min-w-full divide-y divide-gray-200'> | ||
| <thead className='sticky top-0 bg-gray-50 dark:bg-gray-800'> | ||
| <tr> | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🌐 Web query:
playwright npm package latest version 2026💡 Result:
As of February 8, 2026, the latest stable version of the
playwrightnpm package is1.58.1. [1]If you’re looking at pre-releases, there are newer
1.59.0-alphabuilds available. [2]Sources: [1] [2]
Update
playwrightversion from^1.58.2to^1.58.1.Playwright version 1.58.2 does not exist. The latest stable version as of February 2026 is 1.58.1.
🤖 Prompt for AI Agents