Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
96 changes: 96 additions & 0 deletions .github/workflows/pr-review.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
name: PR Review

on:
pull_request:
types: [opened, synchronize, ready_for_review, reopened]

permissions:
contents: read
pull-requests: write

jobs:
review:
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- name: Checkout PR branch
uses: actions/checkout@v4
with:
fetch-depth: 1

- name: Generate Marty token
id: marty-token
uses: actions/create-github-app-token@v2
with:
app-id: ${{ secrets.MARTY_APP_ID }}
private-key: ${{ secrets.MARTY_APP_PRIVATE_KEY }}

- name: Run Marty PR Review
uses: nesalia-inc/marty-action@1.0.0
Copy link

Choose a reason for hiding this comment

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

Using a third-party action from nesalia-inc/marty-action. Ensure this is a trusted action and consider pinning to a specific version tag (e.g., @v1.0.0) instead of a floating tag to prevent unexpected changes.

Copy link

Choose a reason for hiding this comment

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

For security, third-party actions should be pinned to a specific commit SHA rather than a version tag. This prevents potential supply chain attacks if the version tag is redirected. Consider using nesalia-inc/marty-action@a0b1c2d... (replace with actual commit SHA).

with:
github_token: ${{ steps.marty-token.outputs.token }}
prompt: |
REPO: ${{ github.repository }}
PR NUMBER: ${{ github.event.pull_request.number }}
AUTHOR: ${{ github.event.pull_request.user.login }}
TITLE: ${{ github.event.pull_request.title }}

Please review this pull request comprehensively:

## Review Focus Areas

### Code Quality
- Code follows best practices and design patterns
- Proper error handling and edge cases
- No code duplication (DRY principle)
- Clear and maintainable code structure

### Security
- No hardcoded secrets or credentials
- Proper input validation and sanitization
- SQL injection and XSS vulnerabilities
- Authentication and authorization checks
- Sensitive data handling

### Performance
- Potential performance bottlenecks
- Efficient database queries
- Proper caching strategies
- Resource cleanup and memory leaks

### Testing
- Adequate test coverage for changes
- Edge cases covered
- Test quality and assertions

### Documentation
- README updated if needed
- Inline comments for complex logic
- API documentation updated
- Breaking changes documented

## Review Output Format

Use inline comments for specific code issues (highlight exact lines).
Use top-level PR comments for general observations and summary.

Structure your top-level comment as:
- **Summary**: Brief overview
- **Critical Issues**: Must-fix items
- **Recommendations**: Suggestions for improvement
- **Positive Notes**: Good practices observed

Note: The PR branch is already checked out in the current working directory.

Only post GitHub comments - don't submit review text as messages.

claude_args: |
--allowedTools "mcp__github_inline_comment__create_inline_comment,Bash(gh pr comment:*),Bash(gh pr diff:*),Bash(gh pr view:*)"
--max-turns 100

env:
ANTHROPIC_BASE_URL: https://api.minimax.io/anthropic
ANTHROPIC_AUTH_TOKEN: ${{ secrets.MINIMAX_API_KEY }}
ANTHROPIC_DEFAULT_SONNET_MODEL: MiniMax-M2.5
ANTHROPIC_DEFAULT_HAIKU_MODEL: MiniMax-M2.5
ANTHROPIC_DEFAULT_OPUS_MODEL: MiniMax-M2.5
36 changes: 36 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# Dependencies
node_modules/
.pnpm-store/

# Build outputs
dist/
out/
build/

# IDE
.idea/
.vscode/
*.swp
*.swo

# OS
.DS_Store
Thumbs.db

# Logs
*.log
npm-debug.log*

# Environment
.env
.env.local
.env.*.local

# Testing
coverage/

# Turbo
.turbo/

# Misc
*.tsbuildinfo
14 changes: 12 additions & 2 deletions apps/desktop/electron.vite.config.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { defineConfig, externalizeDepsPlugin } from 'electron-vite'
Copy link

Choose a reason for hiding this comment

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

The externalizeDepsPlugin is correctly imported from 'electron-vite' which is the correct import path for version 5.0.0. The configuration looks compatible with electron-vite 5.x.

import { resolve } from 'path'
import tailwindcss from '@tailwindcss/vite'
import tsconfigPaths from 'vite-tsconfig-paths'

export default defineConfig({
main: {
Expand All @@ -23,13 +25,21 @@ export default defineConfig({
}
},
renderer: {
Copy link

Choose a reason for hiding this comment

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

The root change from ../web/dist to ../web is correct for development mode, but ensure production builds still work correctly since dist is the output directory.

Copy link

Choose a reason for hiding this comment

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

Security Issue: The desktop renderer is sharing the same index.html from the web app. This means the desktop app will load the web app's HTML which includes the same CSP policy. However, the desktop app needs to load the actual desktop renderer entry point (/src/main.tsx), not the web app's entry. This configuration could cause the desktop app to fail to load properly or bypass security policies intended for the web app.

root: resolve(__dirname, '../web/dist'),
root: resolve(__dirname, '../web'),
Copy link

Choose a reason for hiding this comment

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

This is a positive change - pointing to the source directory instead of dist allows the desktop app to use the web app's Vite dev server with HMR.

plugins: [
tailwindcss(),
tsconfigPaths({ projects: ['./tsconfig.json'] })
],
build: {
rollupOptions: {
input: {
Copy link

Choose a reason for hiding this comment

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

Issue: The desktop renderer configuration uses the web app's root and index.html, but the desktop renderer should have its own entry point. Currently, both web and desktop will share the same renderer, which may not be the intended behavior for a desktop application.

Recommendation: Consider creating a separate renderer entry point for the desktop app (e.g., apps/desktop/src/renderer/) or ensure this is intentional for your architecture.

index: resolve(__dirname, '../web/dist/index.html')
index: resolve(__dirname, '../web/index.html')
}
}
},
server: {
host: '127.0.0.1',
Copy link

Choose a reason for hiding this comment

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

Both apps are configured to use port 3000. When running dev:all, both the web app and desktop renderer will try to bind to the same port, causing a conflict. Consider assigning different ports (e.g., web on 3000, desktop renderer on 3001) or using the web app's server for the desktop renderer.

port: 3000
}
}
})
7 changes: 5 additions & 2 deletions apps/desktop/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,14 @@
"@electron-toolkit/eslint-config": "^1.0.2",
"@electron-toolkit/eslint-config-ts": "^2.0.0",
"@electron-toolkit/tsconfig": "^1.0.1",
"@tailwindcss/vite": "^4.1.18",
"@types/node": "^22.10.2",
"electron": "^33.3.1",
"electron-builder": "^25.1.8",
"electron-vite": "^2.3.0",
"electron-vite": "^5.0.0",
"tailwindcss": "^4.1.18",
"typescript": "^5.7.2",
"vite": "^7.1.7"
"vite": "^7.1.7",
"vite-tsconfig-paths": "^5.1.4"
}
}
9 changes: 7 additions & 2 deletions apps/web/index.html
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@

<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>web</title>
<meta name="theme-color" content="#0f172a" />
<title>Wareflow</title>
<style>
html {
color-scheme: light dark;
}
</style>
</head>
<body>
<div id="app"></div>
Expand Down
3 changes: 3 additions & 0 deletions apps/web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
"@tanstack/react-router": "^1.132.0",
"@tanstack/react-router-devtools": "^1.132.0",
"@tanstack/router-plugin": "^1.132.0",
"@wareflow/db": "workspace:*",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"cmdk": "^1.1.1",
Expand All @@ -27,6 +28,7 @@
"input-otp": "^1.4.2",
"lucide-react": "^0.545.0",
"next-themes": "^0.4.6",
"papaparse": "^5.5.3",
"radix-ui": "^1.4.3",
"react": "^19.2.0",
"react-day-picker": "^9.13.2",
Expand All @@ -46,6 +48,7 @@
"@testing-library/dom": "^10.4.0",
"@testing-library/react": "^16.2.0",
"@types/node": "^22.10.2",
"@types/papaparse": "^5.5.2",
"@types/react": "^19.2.0",
"@types/react-dom": "^19.2.0",
"@vitejs/plugin-react": "^5.0.4",
Expand Down
165 changes: 165 additions & 0 deletions apps/web/src/components/setup/column-mapping.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
import { useMemo } from 'react'
import { ArrowLeft, ArrowRight, RefreshCw, Columns2 } from 'lucide-react'
import { Button } from '../ui/button'
import { IMPORT_FIELDS, type ColumnMapping, type ImportField, type ParsedData } from '../../types/setup'

interface ColumnMappingProps {
data: ParsedData[]
headers: string[]
columnMapping: ColumnMapping
onBack: () => void
onNext: () => void
onMappingChange: (mapping: ColumnMapping) => void
}

export function ColumnMappingComponent({
data,
headers,
columnMapping,
onBack,
onNext,
onMappingChange,
}: ColumnMappingProps) {
const sampleData = useMemo(() => data.slice(0, 3), [data])

const autoDetectMapping = useMemo(() => {
Copy link

Choose a reason for hiding this comment

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

The auto-detection only examines the first row of data. Consider sampling multiple rows (e.g., first 5) to improve detection accuracy, especially if the first row might have empty values or unusual formatting.

const mapping: ColumnMapping = {}

IMPORT_FIELDS.forEach(field => {
// Try exact match first
const exactMatch = headers.find(
h => h.toLowerCase() === field.label.toLowerCase() ||
h.toLowerCase() === field.key.toLowerCase()
)

if (exactMatch) {
mapping[field.key] = exactMatch
return
}

// Try partial match
const partialMatch = headers.find(
h => h.toLowerCase().includes(field.key.toLowerCase()) ||
field.label.toLowerCase().includes(h.toLowerCase())
)

if (partialMatch) {
mapping[field.key] = partialMatch
}
})

return mapping
}, [headers])

const handleReset = () => {
onMappingChange(autoDetectMapping)
}

const handleHeaderChange = (fieldKey: ImportField['key'], header: string) => {
onMappingChange({ ...columnMapping, [fieldKey]: header })
}

const mappedCount = Object.keys(columnMapping).filter(k => columnMapping[k as ImportField['key']]).length
const requiredMapped = IMPORT_FIELDS
.filter(f => f.required)
.every(f => columnMapping[f.key])

return (
<div className="w-full max-w-4xl mx-auto">
<div className="text-center mb-8">
<h2 className="text-2xl font-bold text-gray-900 dark:text-white mb-2">
Map Your Columns
</h2>
<p className="text-gray-600 dark:text-gray-400">
Match your file columns to the system fields
</p>
</div>

<div className="flex items-center justify-between mb-4">
<div className="flex items-center gap-2 text-sm">
<Columns2 className="w-4 h-4 text-gray-400" />
<span className="text-gray-600 dark:text-gray-400">
{mappedCount} / {IMPORT_FIELDS.length} fields mapped
</span>
</div>
<Button variant="outline" size="sm" onClick={handleReset}>
<RefreshCw className="w-4 h-4 mr-2" />
Auto-detect
</Button>
</div>

<div className="bg-white dark:bg-gray-800 rounded-xl border border-gray-200 dark:border-gray-700 overflow-hidden">
<div className="grid grid-cols-2 gap-4 p-4 border-b border-gray-200 dark:border-gray-700 bg-gray-50 dark:bg-gray-900">
<div className="text-sm font-medium text-gray-500 dark:text-gray-400">
File Columns
</div>
<div className="text-sm font-medium text-gray-500 dark:text-gray-400">
System Fields
</div>
</div>

<div className="divide-y divide-gray-200 dark:divide-gray-700">
{IMPORT_FIELDS.map((field) => (
<div key={field.key} className="grid grid-cols-2 gap-4 p-4 items-center">
<div>
<div className="text-sm font-medium text-gray-900 dark:text-white">
{field.label}
{field.required && <span className="text-red-500 ml-1">*</span>}
</div>
{field.defaultValue && (
<div className="text-xs text-gray-400">
Default: {field.defaultValue}
</div>
)}
</div>
<select
value={columnMapping[field.key] || ''}
onChange={(e) => handleHeaderChange(field.key, e.target.value)}
className="px-3 py-2 bg-white dark:bg-gray-700 border border-gray-300 dark:border-gray-600 rounded-lg text-sm text-gray-900 dark:text-white focus:outline-none focus:ring-2 focus:ring-blue-500"
>
<option value="">-- Select column --</option>
{headers.map((header) => (
<option key={header} value={header}>
{header}
</option>
))}
</select>
</div>
))}
</div>
</div>

{/* Sample data preview */}
<div className="mt-6 p-4 bg-gray-50 dark:bg-gray-900 rounded-lg">
<div className="text-sm font-medium text-gray-700 dark:text-gray-300 mb-3">
Sample Data Preview
</div>
<div className="grid grid-cols-3 gap-4 text-xs">
{IMPORT_FIELDS.slice(0, 3).map((field) => {
const header = columnMapping[field.key]
const sample = header ? sampleData[0]?.[header] : null
return (
<div key={field.key}>
<div className="text-gray-500 dark:text-gray-400">{field.label}</div>
<div className="text-gray-900 dark:text-white font-mono truncate">
{sample || '-'}
</div>
</div>
)
})}
</div>
</div>

<div className="flex justify-between mt-6">
<Button variant="outline" onClick={onBack}>
<ArrowLeft className="w-4 h-4 mr-2" />
Back
</Button>
<Button onClick={onNext} disabled={!requiredMapped}>
Continue
<ArrowRight className="w-4 h-4 ml-2" />
</Button>
</div>
</div>
)
}
Loading
Loading