diff --git a/.github/workflows/pr-lint.yml b/.github/workflows/pr-lint.yml deleted file mode 100644 index e72e8ce..0000000 --- a/.github/workflows/pr-lint.yml +++ /dev/null @@ -1,39 +0,0 @@ -name: PR Lint & Conventional Commits - -on: - pull_request_target: - types: - - opened - - edited - - synchronize - - reopened - -permissions: - pull-requests: read - statuses: write - -jobs: - validate-title: - name: Validate PR Title (Conventional Commits) - runs-on: ubuntu-latest - steps: - - name: Check PR Title Semantics - uses: amannn/action-semantic-pull-request@v5 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - types: | - feat - fix - docs - style - refactor - perf - test - build - ci - chore - revert - requireScope: false - subjectPattern: ^(?![A-Z]).+$ - subjectPatternError: The PR subject should start with a lowercase letter (e.g., 'feat: add dark mode support'). diff --git a/README.md b/README.md index 2239c29..cce3f28 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,10 @@
- ContraGit Logo -

⚡ ContraGit

+ + ContraGit Banner + + +

+

Architect Your GitHub Profile • Precision Contribution Scheduling & Git Activity Studio

@@ -177,42 +181,6 @@ npm run check --- -## 📁 Repository Structure - -``` -gitcontra/ -├── .github/ # GitHub workflows and issue/PR templates -│ ├── ISSUE_TEMPLATE/ # Bug report & feature request templates -│ ├── workflows/ # CI and PR Semantic Lint workflows -│ └── PULL_REQUEST_TEMPLATE.md # Standard PR checklist and guidelines -├── app/ # Next.js 16 App Router application -│ ├── api/ # Serverless Route Handlers -│ │ ├── auth/ # GitHub OAuth login, callback, status, logout -│ │ ├── calculate/ # Commit distribution calculations -│ │ ├── commit-batch/ # Atomic Git Data API micro-batch authoring -│ │ ├── repos/ # Repository list and validation -│ │ └── social-actions/ # Star & follow helpers -│ ├── auth/ # Dedicated authentication view -│ ├── components/ # UI components & Studio architecture -│ │ ├── layout/ # AppHeader, MobileBottomBar -│ │ ├── studio/ # TargetRepoCard, QuickRulesCard, CommitStyleCard -│ │ ├── ui/ # Button, Badge, Card, Tabs -│ │ └── HeatmapCanvas.tsx # 52-Week Interactive Matrix Studio Canvas -│ ├── dashboard/ # Pro Studio workspace page -│ ├── instructions/ # Architecture and usage documentation -│ ├── globals.css # Apple-inspired design tokens and layout primitives -│ ├── layout.tsx # Root layout & ambient backdrop -│ └── page.tsx # Landing page with Live Interactive Stencil Studio -├── extension/ # Manifest V3 browser extension -├── lib/ # Core domain logic and client services -│ ├── services/ # GitHubApiClient, ScheduleEngine, SessionManager -│ ├── types/ # TypeScript interfaces (github, scheduler) -│ └── utils/ # 5x7 PixelFont engine and stencil matrices -└── tests/ # Automated Vitest test suites -``` - ---- - ## 🧩 Browser Extension Setup (Chrome / Brave / Edge) ContraGit ships with an unpackable **Manifest V3** browser extension for quick access: diff --git a/app/auth/page.tsx b/app/auth/page.tsx index a18f9ae..bbfc754 100644 --- a/app/auth/page.tsx +++ b/app/auth/page.tsx @@ -1,11 +1,11 @@ 'use client'; -import React, { Suspense, useEffect, useState } from 'react'; +import { Suspense, useEffect, useState } from 'react'; import Link from 'next/link'; +import Image from 'next/image'; import { useRouter, useSearchParams } from 'next/navigation'; -import { ArrowLeft, AlertCircle, KeyRound, Shield, Loader2, Lock } from 'lucide-react'; +import { ArrowLeft, AlertCircle, Shield, Loader2, Lock } from 'lucide-react'; import { GitHubIcon } from '@/app/components/GitHubIcon'; -import { Button } from '@/app/components/ui/Button'; function AuthContent() { const router = useRouter(); @@ -46,23 +46,29 @@ function AuthContent() { margin: 'auto', }} > - {/* Icon Chip */} + {/* Brand Logo Chip */}

- + ContraGit Logo

diff --git a/app/components/HeatmapCanvas.tsx b/app/components/HeatmapCanvas.tsx index 49f4d85..7f6e887 100644 --- a/app/components/HeatmapCanvas.tsx +++ b/app/components/HeatmapCanvas.tsx @@ -1,6 +1,6 @@ 'use client'; -import React, { useState, useEffect, useMemo, useCallback, useRef } from 'react'; +import { useState, useEffect, useMemo, useCallback, useRef } from 'react'; import { CustomCommitMatrix } from '@/lib/types/scheduler'; import { renderTextToPixelMatrix, STENCILS } from '@/lib/utils/pixelFont'; import { diff --git a/app/components/layout/AppHeader.tsx b/app/components/layout/AppHeader.tsx index 5f5af2f..606327d 100644 --- a/app/components/layout/AppHeader.tsx +++ b/app/components/layout/AppHeader.tsx @@ -1,7 +1,8 @@ 'use client'; -import React, { useState, useEffect } from 'react'; +import { useState, useEffect } from 'react'; import Link from 'next/link'; +import Image from 'next/image'; import { useRouter } from 'next/navigation'; import { ArrowRight, @@ -10,8 +11,7 @@ import { ExternalLink, LogOut, Menu, - X, - Sparkles + X } from 'lucide-react'; import { GitHubUser } from '@/lib/types/github'; import { GitHubIcon } from '@/app/components/GitHubIcon'; @@ -80,7 +80,14 @@ export function AppHeader({ user, activeNav = 'dashboard', defaultMobileOpen = f aria-label="ContraGit Home" >
diff --git a/app/components/layout/MobileBottomBar.tsx b/app/components/layout/MobileBottomBar.tsx index db1edbf..ffb5c05 100644 --- a/app/components/layout/MobileBottomBar.tsx +++ b/app/components/layout/MobileBottomBar.tsx @@ -1,7 +1,6 @@ 'use client'; -import React from 'react'; -import { ArrowRight, Sparkles } from 'lucide-react'; +import { ArrowRight } from 'lucide-react'; import { Button } from '@/app/components/ui/Button'; export interface MobileBottomBarProps { diff --git a/app/components/studio/AmbientDeployIsland.tsx b/app/components/studio/AmbientDeployIsland.tsx new file mode 100644 index 0000000..0a143cd --- /dev/null +++ b/app/components/studio/AmbientDeployIsland.tsx @@ -0,0 +1,171 @@ +'use client'; + +import { useState, useEffect } from 'react'; +import { + Loader2, + Pause, + Play, + CheckCircle2, + ExternalLink, + ChevronUp +} from 'lucide-react'; +import { DeploymentStage } from './DeploymentControlHub'; +import { CommitJob } from '@/lib/types/scheduler'; + +export interface AmbientDeployIslandProps { + stage: DeploymentStage; + job: CommitJob | null; + repoOwner: string; + selectedRepo: string; + branch: string; + onPause: () => void; + onResume: () => void; +} + +export function AmbientDeployIsland({ + stage, + job, + repoOwner, + selectedRepo, + branch, + onPause, + onResume, +}: AmbientDeployIslandProps) { + const [isScrolledAway, setIsScrolledAway] = useState(false); + + useEffect(() => { + const handleScroll = () => { + const hubEl = document.getElementById('deployment-hub'); + if (!hubEl) { + setIsScrolledAway(true); + return; + } + const rect = hubEl.getBoundingClientRect(); + // If the deployment hub is out of the primary viewport (scrolled above or below) + const isVisible = rect.top < window.innerHeight && rect.bottom > 0; + setIsScrolledAway(!isVisible); + }; + + window.addEventListener('scroll', handleScroll, { passive: true }); + handleScroll(); + return () => window.removeEventListener('scroll', handleScroll); + }, []); + + // Only display when a job is active or completed AND user is looking elsewhere + const shouldShow = (stage === 'deploying' || stage === 'paused' || stage === 'completed') && isScrolledAway && job; + if (!shouldShow || !job) return null; + + const progressPercent = job.total > 0 ? Math.round((job.completed / job.total) * 100) : 0; + const isPaused = stage === 'paused'; + const isCompleted = stage === 'completed'; + + const scrollToHub = () => { + const hubEl = document.getElementById('deployment-hub'); + if (hubEl) { + hubEl.scrollIntoView({ behavior: 'smooth', block: 'center' }); + } + }; + + return ( +
+ {/* Icon Indicator */} +
+ {isCompleted ? ( + + ) : isPaused ? ( + + ) : ( + + )} +
+ + {/* Main Info */} +
+
+ + {isCompleted ? 'Deployment Complete' : `${job.completed} / ${job.total} commits`} + + + ({progressPercent}%) + +
+
+ {selectedRepo}:{branch} +
+
+ + {/* Controls or Deep Link */} +
e.stopPropagation()}> + {isCompleted ? ( + + GitHub + + ) : isPaused ? ( + + ) : ( + + )} + +
+
+ ); +} diff --git a/app/components/studio/BatchProgressModal.tsx b/app/components/studio/BatchProgressModal.tsx deleted file mode 100644 index 725ca8e..0000000 --- a/app/components/studio/BatchProgressModal.tsx +++ /dev/null @@ -1,202 +0,0 @@ -'use client'; - -import React from 'react'; -import { - CheckCircle, - ExternalLink, - Loader2, - Pause, - Play, - RotateCcw, - X -} from 'lucide-react'; -import { Button } from '@/app/components/ui/Button'; -import { CommitJob } from '@/lib/types/scheduler'; - -export interface BatchProgressModalProps { - job: CommitJob | null; - repoOwner: string; - selectedRepo: string; - branch: string; - hasBatchState: boolean; - onPause: () => void; - onResume: () => void; - onStopAndKeep: () => void; - onClose: () => void; -} - -export function BatchProgressModal({ - job, - repoOwner, - selectedRepo, - branch, - hasBatchState, - onPause, - onResume, - onStopAndKeep, - onClose, -}: BatchProgressModalProps) { - if (!job) return null; - - const progressPercent = job.total > 0 ? Math.round((job.completed / job.total) * 100) : 0; - - return ( -
-
- {/* Modal Header */} -
- {job.status === 'completed' ? ( - <> - - - - ) : job.status === 'paused' ? ( - <> - - - - ) : job.status === 'failed' ? ( - <> - - - - ) : ( - <> - - - - )} -
- -

- Target: {repoOwner}/{selectedRepo}:{branch} -

- - {/* Progress Bar */} -
-
-
- -
- Progress: {job.completed} / {job.total} - {progressPercent}% -
- - {/* Paused Banner */} - {job.status === 'paused' && ( -
-
- Status: {job.error || `Paused at ${job.completed} / ${job.total} commits. Commits up to this checkpoint are permanently saved to GitHub.`} -
-
- )} - - {/* Error Banner */} - {job.status === 'failed' && ( -
-
- Error: {job.error} -
-
- )} - - {/* Success Banner */} - {job.status === 'completed' && ( -
-
{job.result?.message}
- {job.result?.pushResult?.message && ( -
- {job.result.pushResult.message} -
- )} -
- )} - - {/* Controls Footer */} -
- {job.status === 'paused' ? ( - <> - - - - ) : job.status === 'completed' ? ( - <> - - - View on GitHub - - - ) : job.status === 'failed' ? ( - <> - - {hasBatchState && ( - - )} - - ) : ( -
- - Deploying in micro-batches... - - -
- )} -
-
-
- ); -} diff --git a/app/components/studio/CommitStyleCard.tsx b/app/components/studio/CommitStyleCard.tsx index 1d1f72b..5c9dae8 100644 --- a/app/components/studio/CommitStyleCard.tsx +++ b/app/components/studio/CommitStyleCard.tsx @@ -1,6 +1,5 @@ 'use client'; -import React from 'react'; import { Card } from '@/app/components/ui/Card'; import { CommitMessageStyle } from '@/lib/types/scheduler'; import { Badge } from '@/app/components/ui/Badge'; diff --git a/app/components/studio/DeploymentControlHub.tsx b/app/components/studio/DeploymentControlHub.tsx new file mode 100644 index 0000000..9d2a71f --- /dev/null +++ b/app/components/studio/DeploymentControlHub.tsx @@ -0,0 +1,497 @@ +'use client'; + +import { + Play, + Pause, + RotateCcw, + CheckCircle2, + ExternalLink, + Loader2, + Sparkles, + GitCommit, + Rocket, + AlertTriangle +} from 'lucide-react'; +import { Button } from '@/app/components/ui/Button'; +import { Badge } from '@/app/components/ui/Badge'; +import { CommitJob, ScheduleCalculation } from '@/lib/types/scheduler'; +import { TargetLanguageConfig } from '@/lib/types/language'; + +export type DeploymentStage = + | 'idle' + | 'review' + | 'deploying' + | 'paused' + | 'completed' + | 'failed'; + +export interface DeploymentControlHubProps { + stage: DeploymentStage; + repoOwner: string; + selectedRepo: string; + branch: string; + effectiveTotal: number; + effectiveDays: number; + originalPreservedDays: number; + activeMode: 'canvas' | 'standard'; + languageConfig?: TargetLanguageConfig; + calculation: ScheduleCalculation | null; + calculating: boolean; + job: CommitJob | null; + hasBatchState: boolean; + onReviewPlan: () => void; + onCancelReview: () => void; + onConfirmAndDeploy: () => void; + onPause: () => void; + onResume: () => void; + onStopAndKeep: () => void; + onReset: () => void; +} + +export function DeploymentControlHub({ + stage, + repoOwner, + selectedRepo, + branch, + effectiveTotal, + effectiveDays, + originalPreservedDays, + activeMode, + languageConfig, + calculation, + calculating, + job, + hasBatchState, + onReviewPlan, + onCancelReview, + onConfirmAndDeploy, + onPause, + onResume, + onStopAndKeep, + onReset, +}: DeploymentControlHubProps) { + const currentTotal = job ? job.total : (calculation?.totalCommits ?? effectiveTotal); + const currentCompleted = job ? job.completed : 0; + const progressPercent = currentTotal > 0 ? Math.round((currentCompleted / currentTotal) * 100) : 0; + + // ------------------------------------------------------------- + // STAGE 4: COMPLETED + // ------------------------------------------------------------- + if (stage === 'completed') { + return ( +
+
+
+
+ +
+

+ Schedule Completed! +

+ + 100% of commits generated and verified + +
+
+ Pushed to Remote +
+ +
+
+ Destination: + + {repoOwner}/{selectedRepo}:{branch} + +
+
+ Authored Volume: + {job?.total || effectiveTotal} commits +
+ {job?.result?.pushResult?.message && ( +
+ {job.result.pushResult.message} +
+ )} +
+
+ +
+ + + View on GitHub + +
+
+ ); + } + + // ------------------------------------------------------------- + // STAGE 3: DEPLOYING / PAUSED / FAILED (LIVE EXECUTION) + // ------------------------------------------------------------- + if (stage === 'deploying' || stage === 'paused' || stage === 'failed') { + const isPaused = stage === 'paused'; + const isFailed = stage === 'failed'; + + return ( +
+
+ {/* Header */} +
+
+ {isFailed ? ( + + ) : isPaused ? ( + + ) : ( + + )} +
+

+ {isFailed ? 'Generation Halted' : isPaused ? 'Generation Paused' : 'Deploying Micro-Batches...'} +

+ + Target: {repoOwner}/{selectedRepo}:{branch} + +
+
+ + {isFailed ? 'Failed' : isPaused ? 'Paused' : `${progressPercent}%`} + +
+ + {/* Progress Bar */} +
+
+
+
+
+ Progress: {currentCompleted} / {currentTotal} commits + Batch {Math.ceil(currentCompleted / 5)} / {Math.ceil(currentTotal / 5)} +
+
+ + {/* Status Details / Errors */} + {isPaused && ( +
+
+ Paused: Commits up to #{currentCompleted} are safely recorded on GitHub. +
+
+ )} + + {isFailed && ( +
+
+ Error: {job?.error || 'Batch generation encountered an error.'} +
+
+ )} + + {!isPaused && !isFailed && ( +
+ ⚡ Stateless Git Data API pipeline active +
+ Safe to close tab or switch apps. Checkpoints sync every 5 commits. +
+
+ )} +
+ + {/* Action Controls */} +
+ {isPaused ? ( + <> + + + + ) : isFailed ? ( + <> + + {hasBatchState && ( + + )} + + ) : ( + + )} +
+
+ ); + } + + // ------------------------------------------------------------- + // STAGE 2: INLINE PRE-FLIGHT REVIEW + // ------------------------------------------------------------- + if (stage === 'review' && calculation) { + return ( +
+
+
+
+ +
+

+ Pre-Flight Verification +

+ + Confirm commit parameters before pushing + +
+
+ Ready to Author +
+ +
+
+ Mode: + + {activeMode === 'canvas' ? '🎨 Visual Canvas' : '⚡ Quick Rules'} + +
+
+ Target: + + {calculation.repository} ({branch}) + +
+ {languageConfig && ( +
+ Linguist Target: + + {languageConfig.mode === 'preset' ? (languageConfig.preset || 'fullstack') : (languageConfig.language || 'typescript')} + +
+ )} +
+ Date Coverage: + {calculation.dateRange} +
+
+ Active Days: + {calculation.eligibleDays} days +
+ {(calculation.protectedDays || 0) > 0 && ( +
+ Original Protected: + + 🔒 {calculation.protectedDays} days preserved + +
+ )} +
+ Commits to Author: + + {calculation.totalCommits} commits + +
+
+ Execution Pipeline: + + {Math.ceil(calculation.totalCommits / 5)} micro-batches (~{calculation.estimatedSeconds}s) + +
+
+
+ +
+ + +
+
+ ); + } + + // ------------------------------------------------------------- + // STAGE 1: IDLE / READY (RESTING DASHBOARD STATE) + // ------------------------------------------------------------- + return ( +
+
+
+
+ + Authoring Queue + +
+ {effectiveTotal} commits +
+
+ }> + Ready to Push + +
+ +
+
+ Target Repository: + {repoOwner}/{selectedRepo || 'repo'} +
+
+ Target Branch: + {branch} +
+
+ Active Days: + {effectiveDays} days +
+ {originalPreservedDays > 0 && ( +
+ Original Preserved: + 🔒 {originalPreservedDays} days +
+ )} +
+ Safety: + Stateless 5-commit chunks +
+
+
+ + +
+ ); +} diff --git a/app/components/studio/LanguageSelectorCard.tsx b/app/components/studio/LanguageSelectorCard.tsx index aba032e..d49d872 100644 --- a/app/components/studio/LanguageSelectorCard.tsx +++ b/app/components/studio/LanguageSelectorCard.tsx @@ -1,18 +1,13 @@ 'use client'; -import React from 'react'; import { Card } from '@/app/components/ui/Card'; import { Badge } from '@/app/components/ui/Badge'; -import { - TargetLanguageConfig, - TargetLanguageId, - TargetPresetId -} from '@/lib/types/language'; +import { TargetLanguageConfig } from '@/lib/types/language'; import { LanguageMasqueradeEngine, languageMasqueradeEngine } from '@/lib/services/LanguageMasqueradeEngine'; -import { Code2, Layers, Terminal, Sparkles, Check } from 'lucide-react'; +import { Code2, Layers, Terminal, Check } from 'lucide-react'; export interface LanguageSelectorCardProps { config: TargetLanguageConfig; diff --git a/app/components/studio/QuickRulesCard.tsx b/app/components/studio/QuickRulesCard.tsx index 4d1bd64..c646552 100644 --- a/app/components/studio/QuickRulesCard.tsx +++ b/app/components/studio/QuickRulesCard.tsx @@ -1,6 +1,5 @@ 'use client'; -import React from 'react'; import { Calendar, Minus, Plus } from 'lucide-react'; import { Card } from '@/app/components/ui/Card'; import { Badge } from '@/app/components/ui/Badge'; diff --git a/app/components/studio/TargetRepoCard.tsx b/app/components/studio/TargetRepoCard.tsx index e759e9e..59a892e 100644 --- a/app/components/studio/TargetRepoCard.tsx +++ b/app/components/studio/TargetRepoCard.tsx @@ -1,7 +1,7 @@ 'use client'; -import React, { useState, useMemo } from 'react'; -import { GitBranch, RefreshCw, Lock, FolderGit2, Search, PlusCircle, Check } from 'lucide-react'; +import { useState, useMemo } from 'react'; +import { GitBranch, RefreshCw, FolderGit2, Search } from 'lucide-react'; import { Card } from '@/app/components/ui/Card'; import { Badge } from '@/app/components/ui/Badge'; import { GitHubRepository } from '@/lib/types/github'; diff --git a/app/dashboard/page.tsx b/app/dashboard/page.tsx index 558d506..a7b1ee6 100644 --- a/app/dashboard/page.tsx +++ b/app/dashboard/page.tsx @@ -1,21 +1,15 @@ 'use client'; -import Link from 'next/link'; import { useEffect, useState, useMemo, useRef } from 'react'; import { useRouter } from 'next/navigation'; import { - Compass, - GitCommit, Palette, - Play, - Sparkles, Sliders, Star, UserPlus } from 'lucide-react'; import { - Weekday, WeekdayCounts, FilterPreset, FilterOption, @@ -32,8 +26,6 @@ import { AppHeader } from '@/app/components/layout/AppHeader'; import { MobileBottomBar } from '@/app/components/layout/MobileBottomBar'; import { Tabs } from '@/app/components/ui/Tabs'; import { Button } from '@/app/components/ui/Button'; -import { Badge } from '@/app/components/ui/Badge'; -import { Card } from '@/app/components/ui/Card'; import { HeatmapCanvas } from '@/app/components/HeatmapCanvas'; import { TargetLanguageConfig } from '@/lib/types/language'; @@ -41,7 +33,8 @@ import { LanguageSelectorCard } from '@/app/components/studio/LanguageSelectorCa import { TargetRepoCard } from '@/app/components/studio/TargetRepoCard'; import { CommitStyleCard } from '@/app/components/studio/CommitStyleCard'; import { QuickRulesCard } from '@/app/components/studio/QuickRulesCard'; -import { BatchProgressModal } from '@/app/components/studio/BatchProgressModal'; +import { DeploymentControlHub, DeploymentStage } from '@/app/components/studio/DeploymentControlHub'; +import { AmbientDeployIsland } from '@/app/components/studio/AmbientDeployIsland'; export default function DashboardPage() { const router = useRouter(); @@ -95,20 +88,18 @@ export default function DashboardPage() { const [activeFilter, setActiveFilter] = useState('all'); const [activeOption, setActiveOption] = useState('max'); - // Pre-flight calculation & summary modal + // Pre-flight calculation & deployment lifecycle state machine const [calculating, setCalculating] = useState(false); const [calculation, setCalculation] = useState(null); - const [showSummaryModal, setShowSummaryModal] = useState(false); + const [deploymentStage, setDeploymentStage] = useState('idle'); // Execution State & Batch Pipeline const [activeJob, setActiveJob] = useState(null); - const [showProgressModal, setShowProgressModal] = useState(false); const [startingJob, setStartingJob] = useState(false); // Original GitHub contributions & protection const [existingContributions, setExistingContributions] = useState>({}); const [protectExisting, setProtectExisting] = useState(true); - const [loadingContributions, setLoadingContributions] = useState(false); const isPausedRef = useRef(false); const cancelRef = useRef(false); @@ -167,7 +158,6 @@ export default function DashboardPage() { }; const loadContributions = async () => { - setLoadingContributions(true); try { const res = await fetch('/api/contributions'); const data = await res.json(); @@ -176,8 +166,6 @@ export default function DashboardPage() { } } catch (err) { console.warn('Failed to load contributions:', err); - } finally { - setLoadingContributions(false); } }; @@ -237,7 +225,12 @@ export default function DashboardPage() { const calcResult = data.calculation || data; if (data.success && calcResult) { setCalculation(calcResult); - setShowSummaryModal(true); + setDeploymentStage('review'); + // Smoothly bring the deployment cockpit into view + const hubEl = document.getElementById('deployment-hub'); + if (hubEl) { + hubEl.scrollIntoView({ behavior: 'smooth', block: 'center' }); + } } else { alert(data.message || 'Failed to calculate schedule'); } @@ -337,6 +330,7 @@ export default function DashboardPage() { }, }, } : null); + setDeploymentStage('completed'); batchStateRef.current = null; } catch (err: any) { console.warn('[ContraGit] Batch error or sleep interruption:', err); @@ -353,13 +347,13 @@ export default function DashboardPage() { completed, error: `Connection paused or interrupted (${err.message || 'network drop'}). Saved ${completed} commits to GitHub.`, } : null); + setDeploymentStage('paused'); } }; const handleConfirmAndStart = async () => { - setShowSummaryModal(false); setStartingJob(true); - setShowProgressModal(true); + setDeploymentStage('deploying'); try { const lockedDates = Object.keys(existingContributions).filter(d => (existingContributions[d] || 0) > 0); @@ -432,7 +426,7 @@ export default function DashboardPage() { await runBatchLoop(allCommits, 0, null, null); } catch (err: any) { alert(err.message || 'Failed to start generation job'); - setShowProgressModal(false); + setDeploymentStage('failed'); } finally { setStartingJob(false); } @@ -440,19 +434,31 @@ export default function DashboardPage() { const handlePauseGeneration = () => { isPausedRef.current = true; + setDeploymentStage('paused'); }; const handleResumeGeneration = async () => { if (!batchStateRef.current) return; const { allCommits, currentIndex, lastCommitSha, lastTreeSha } = batchStateRef.current; setActiveJob(prev => prev ? { ...prev, status: 'running', error: undefined } : null); + setDeploymentStage('deploying'); await runBatchLoop(allCommits, currentIndex, lastCommitSha, lastTreeSha); }; const handleStopAndKeep = () => { batchStateRef.current = null; - setShowProgressModal(false); + setDeploymentStage('idle'); + setActiveJob(null); + }; + + const handleCancelReview = () => { + setDeploymentStage('idle'); + }; + + const handleResetHub = () => { + setDeploymentStage('idle'); setActiveJob(null); + setCalculation(null); }; const triggerSocialAction = async (action: 'star' | 'follow') => { @@ -599,75 +605,29 @@ export default function DashboardPage() { onCustomPrefixChange={setCustomMessagePrefix} /> - {/* Right Column: Deployment HUD & Action Center */} -
-
-
-
- - Authoring Queue - -
- {effectiveTotal} commits -
-
- }> - Ready to Push - -
- -
-
- Target Repository: - {repoOwner}/{selectedRepo || 'repo'} -
-
- Target Branch: - {branch} -
-
- Active Days: - {effectiveDays} days -
-
- Safety: - Stateless 5-commit chunks -
-
-
- - -
+ {/* Right Column: Integrated Stateful Deployment Control Hub */} +
{/* Voluntary Support / Social Feedback */} @@ -714,100 +674,15 @@ export default function DashboardPage() {
- {/* Pre-Flight Review Modal */} - {showSummaryModal && calculation && ( -
-
-
- -

Schedule Pre-Flight Summary

-
- -
-
- Mode: - - {activeMode === 'canvas' ? '🎨 Visual Canvas Studio' : '⚡ Quick Rules'} - -
-
- Target: - {calculation.repository} ({branch}) -
-
- Date Coverage: - {calculation.dateRange} -
-
- Active Days: - {calculation.eligibleDays} days -
-
- Commits to Author: - {calculation.totalCommits} commits -
- {calculation.protectedDays && calculation.protectedDays > 0 ? ( -
- Original Activity: - - 🔒 {calculation.protectedDays} days preserved - -
- ) : null} -
- Execution Pipeline: - - Stateless Micro-Batches (~{Math.ceil(calculation.totalCommits / 5)} requests) - -
-
- -
- - -
-
-
- )} - - {/* Live Batched Execution Modal */} - { - setShowProgressModal(false); - setActiveJob(null); - }} /> {/* Mobile Sticky Bottom Bar */} diff --git a/app/globals.css b/app/globals.css index 8daeeb8..157554e 100644 --- a/app/globals.css +++ b/app/globals.css @@ -944,15 +944,19 @@ p { width: 36px; height: 36px; border-radius: var(--radius-md); - background: linear-gradient(135deg, #10b981 0%, #0284c7 100%); display: flex; align-items: center; justify-content: center; box-shadow: 0 0 16px var(--emerald-glow); - font-weight: 800; - font-size: 1.1rem; - color: #03140c; flex-shrink: 0; + overflow: hidden; +} + +.app-header__logo-img { + width: 100%; + height: 100%; + object-fit: cover; + border-radius: inherit; } .app-header__title-group { diff --git a/app/instructions/page.tsx b/app/instructions/page.tsx index 564a57f..520f3d6 100644 --- a/app/instructions/page.tsx +++ b/app/instructions/page.tsx @@ -1,6 +1,5 @@ -import React from 'react'; import Link from 'next/link'; -import { ArrowLeft, ArrowRight, CheckCircle2, GitBranch, KeyRound, Shield, Sparkles } from 'lucide-react'; +import { ArrowLeft, ArrowRight, CheckCircle2, GitBranch, Shield, Sparkles } from 'lucide-react'; import { AppHeader } from '@/app/components/layout/AppHeader'; import { Card } from '@/app/components/ui/Card'; import { Badge } from '@/app/components/ui/Badge'; diff --git a/app/layout.tsx b/app/layout.tsx index 8299459..47fc98a 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -5,6 +5,17 @@ export const metadata: Metadata = { title: 'ContraGit — Architect Your GitHub Activity', description: 'Precision GitHub contribution scheduling, organic activity simulation, and commit automation engine.', keywords: ['github', 'contribution-graph', 'automation', 'developer-tools', 'git', 'streak', 'contragit'], + icons: { + icon: [ + { url: '/favicon/favicon.ico' }, + { url: '/favicon/favicon.svg', type: 'image/svg+xml' }, + { url: '/favicon/favicon-96x96.png', sizes: '96x96', type: 'image/png' }, + ], + apple: [ + { url: '/favicon/apple-touch-icon.png', sizes: '180x180', type: 'image/png' }, + ], + }, + manifest: '/favicon/site.webmanifest', }; export default function RootLayout({ diff --git a/app/page.tsx b/app/page.tsx index 19f5aa5..a25615b 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -1,33 +1,19 @@ 'use client'; -import React, { useEffect, useState, useMemo } from 'react'; +import { useEffect, useState, useMemo } from 'react'; import Link from 'next/link'; import { ArrowRight, - Calendar, - Check, - CheckCircle2, - Code2, Cpu, - ExternalLink, - Flame, - GitBranch, GitCommit, - Layers, Lock, Palette, - Play, - RefreshCw, - Shield, - ShieldCheck, Smartphone, Sparkles, - Terminal, Type, Zap } from 'lucide-react'; import { AppHeader } from '@/app/components/layout/AppHeader'; -import { Button } from '@/app/components/ui/Button'; import { Badge } from '@/app/components/ui/Badge'; import { Card } from '@/app/components/ui/Card'; import { GitHubUser } from '@/lib/types/github'; @@ -43,16 +29,14 @@ const LEVEL_COLORS: Record = { export default function HomePage() { const [user, setUser] = useState(null); - const [mounted, setMounted] = useState(false); // Hero interactive typing studio const [stampInput, setStampInput] = useState('HIRE ME'); - const [selectedBrush, setSelectedBrush] = useState(3); + const selectedBrush = 3; const [activePreset, setActivePreset] = useState<'HIRE ME' | 'SHIP IT' | 'CONTRA' | 'HEART' | 'INVADER' | 'ORGANIC' | 'CUSTOM'>('HIRE ME'); const [customTiles, setCustomTiles] = useState>({}); useEffect(() => { - setMounted(true); fetch('/api/auth/status') .then((res) => res.json()) .then((data) => { diff --git a/lib/services/CommitJobManager.ts b/lib/services/CommitJobManager.ts index e005858..8fb3fa2 100644 --- a/lib/services/CommitJobManager.ts +++ b/lib/services/CommitJobManager.ts @@ -6,12 +6,10 @@ import { CommitBatchResponse } from '../types/scheduler'; import { GitHubApiClient } from './GitHubApiClient'; -import { ScheduleEngine } from './ScheduleEngine'; import { languageMasqueradeEngine } from './LanguageMasqueradeEngine'; export class CommitJobManager { private jobs: Map = new Map(); - private readonly scheduleEngine: ScheduleEngine; private static readonly SAMPLE_MESSAGES = [ 'feat: implement core workflow updates', @@ -25,10 +23,6 @@ export class CommitJobManager { 'ci: optimize deployment workflow pipeline', ]; - constructor(scheduleEngine: ScheduleEngine = new ScheduleEngine()) { - this.scheduleEngine = scheduleEngine; - } - public createJob(user: GitHubUser, config: ScheduleConfig, totalCommits: number): CommitJob { const jobId = `job_${Date.now()}_${Math.random().toString(36).substring(2, 8)}`; const job: CommitJob = { diff --git a/lib/services/LanguageMasqueradeEngine.ts b/lib/services/LanguageMasqueradeEngine.ts index 55e6554..3b5fe1d 100644 --- a/lib/services/LanguageMasqueradeEngine.ts +++ b/lib/services/LanguageMasqueradeEngine.ts @@ -248,7 +248,7 @@ export function determinant2x2(m: Matrix2D): number { path: 'src/utils/formatters.ts', language: 'TypeScript', template: { - scaffold: (c, t) => `/** + scaffold: (_c, t) => `/** * @file Data Formatters & Unit Conversions * @timestamp ${t} */ @@ -338,7 +338,7 @@ def detect_spikes(analyzer: TimeSeriesAnalyzer, threshold_sigma: float = 2.5) -> path: 'src/models/entities.py', language: 'Python', template: { - scaffold: (c, t) => `""" + scaffold: (_c, t) => `""" Entity Data Models & Validation Timestamp: ${t} """ diff --git a/public/contragit-banner.png b/public/contragit-banner.png new file mode 100644 index 0000000..dd336f2 Binary files /dev/null and b/public/contragit-banner.png differ diff --git a/public/contragit.png b/public/contragit.png new file mode 100644 index 0000000..9477e46 Binary files /dev/null and b/public/contragit.png differ diff --git a/public/favicon/apple-touch-icon.png b/public/favicon/apple-touch-icon.png new file mode 100644 index 0000000..88d9578 Binary files /dev/null and b/public/favicon/apple-touch-icon.png differ diff --git a/public/favicon/favicon-96x96.png b/public/favicon/favicon-96x96.png new file mode 100644 index 0000000..b932a99 Binary files /dev/null and b/public/favicon/favicon-96x96.png differ diff --git a/public/favicon/favicon.ico b/public/favicon/favicon.ico new file mode 100644 index 0000000..64a1edd Binary files /dev/null and b/public/favicon/favicon.ico differ diff --git a/public/favicon/favicon.svg b/public/favicon/favicon.svg new file mode 100644 index 0000000..1c909c0 --- /dev/null +++ b/public/favicon/favicon.svg @@ -0,0 +1 @@ +RealFaviconGeneratorhttps://realfavicongenerator.net \ No newline at end of file diff --git a/public/favicon/site.webmanifest b/public/favicon/site.webmanifest new file mode 100644 index 0000000..38c6b0c --- /dev/null +++ b/public/favicon/site.webmanifest @@ -0,0 +1,21 @@ +{ + "name": "ContraGit", + "short_name": "ContraGit", + "icons": [ + { + "src": "/favicon/web-app-manifest-192x192.png", + "sizes": "192x192", + "type": "image/png", + "purpose": "maskable" + }, + { + "src": "/favicon/web-app-manifest-512x512.png", + "sizes": "512x512", + "type": "image/png", + "purpose": "maskable" + } + ], + "theme_color": "#0a0d12", + "background_color": "#0a0d12", + "display": "standalone" +} \ No newline at end of file diff --git a/public/favicon/web-app-manifest-192x192.png b/public/favicon/web-app-manifest-192x192.png new file mode 100644 index 0000000..4ca3dfc Binary files /dev/null and b/public/favicon/web-app-manifest-192x192.png differ diff --git a/public/favicon/web-app-manifest-512x512.png b/public/favicon/web-app-manifest-512x512.png new file mode 100644 index 0000000..6373b5b Binary files /dev/null and b/public/favicon/web-app-manifest-512x512.png differ diff --git a/tests/CommitBatch.test.ts b/tests/CommitBatch.test.ts index 1147688..8af1854 100644 --- a/tests/CommitBatch.test.ts +++ b/tests/CommitBatch.test.ts @@ -19,7 +19,7 @@ describe('CommitJobManager - executeCommitBatch', () => { const mockClient = { createBlob: vi.fn().mockResolvedValue({ sha: 'mock_blob_sha' }), createTree: vi.fn().mockResolvedValue({ sha: 'mock_tree_sha' }), - createCommit: vi.fn().mockImplementation((owner, repo, msg, treeSha, parents) => { + createCommit: vi.fn().mockImplementation((_owner, _repo, _msg, _treeSha, parents) => { return Promise.resolve({ sha: `mock_commit_${parents[0] || 'root'}` }); }), updateBranchRef: vi.fn().mockResolvedValue(undefined), diff --git a/tests/GitHubApiClient.test.ts b/tests/GitHubApiClient.test.ts index b6e6969..8966690 100644 --- a/tests/GitHubApiClient.test.ts +++ b/tests/GitHubApiClient.test.ts @@ -1,4 +1,4 @@ -import { describe, it, expect, vi } from 'vitest'; +import { describe, it, expect } from 'vitest'; import { GitHubApiClient, GitHubApiError } from '../lib/services/GitHubApiClient'; describe('GitHubApiClient', () => { diff --git a/tests/pixelFont.test.ts b/tests/pixelFont.test.ts index e5df3e2..4193746 100644 --- a/tests/pixelFont.test.ts +++ b/tests/pixelFont.test.ts @@ -1,5 +1,5 @@ import { describe, it, expect } from 'vitest'; -import { renderTextToPixelMatrix, PIXEL_FONT, STENCILS } from '../lib/utils/pixelFont'; +import { renderTextToPixelMatrix, STENCILS } from '../lib/utils/pixelFont'; describe('pixelFont', () => { it('renders standard letters into 7-row boolean matrix', () => {