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
+
+
+
+
+
+
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 */}
-
+
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' ? (
- <>
-
-
Schedule Completed!
- >
- ) : job.status === 'paused' ? (
- <>
-
-
Generation Paused
- >
- ) : job.status === 'failed' ? (
- <>
-
-
Generation Failed
- >
- ) : (
- <>
-
-
Deploying Batched Commits...
- >
- )}
-
-
-
- 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' ? (
- <>
-
-
}
- >
- Resume Generation
-
- >
- ) : job.status === 'completed' ? (
- <>
-
-
- View on GitHub
-
- >
- ) : job.status === 'failed' ? (
- <>
-
- {hasBatchState && (
-
}
- >
- Retry Remaining
-
- )}
- >
- ) : (
-
-
- Deploying in micro-batches...
-
- }
- >
- Pause
-
-
- )}
-
-
-
- );
-}
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}
+
+ )}
+
+
+
+
+
+ );
+ }
+
+ // -------------------------------------------------------------
+ // 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 ? (
+ <>
+
+ }>
+ Resume Batch
+
+ >
+ ) : isFailed ? (
+ <>
+
+ {hasBatchState && (
+ }>
+ Retry Remaining
+
+ )}
+ >
+ ) : (
+ }>
+ Pause Generation
+
+ )}
+
+
+ );
+ }
+
+ // -------------------------------------------------------------
+ // 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)
+
+
+
+
+
+
+
+ }
+ >
+ Confirm & Deploy
+
+
+
+ );
+ }
+
+ // -------------------------------------------------------------
+ // 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
+
+
+
+
+
}
+ style={{ width: '100%', minHeight: '50px', fontSize: '1.05rem' }}
+ >
+ Review & Deploy Schedule
+
+
+ );
+}
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
-
-
-
-
-
}
- style={{ width: '100%', minHeight: '50px', fontSize: '1.05rem' }}
- >
- Deploy Schedule
-
-
+ {/* 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 && (
-