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
39 changes: 0 additions & 39 deletions .github/workflows/pr-lint.yml

This file was deleted.

44 changes: 6 additions & 38 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
<div align="center">
<img src="https://raw.githubusercontent.com/Interittus13/gitcontra/main/app/favicon.ico" alt="ContraGit Logo" width="64" height="64" onerror="this.style.display='none'"/>
<h1>⚑ ContraGit</h1>
<a href="https://github.com/Interittus13/gitcontra">
<img src="public/contragit-banner.png" alt="ContraGit Banner" width="100%" />
</a>

<br/><br/>

<p><strong>Architect Your GitHub Profile β€’ Precision Contribution Scheduling & Git Activity Studio</strong></p>

<p>
Expand Down Expand Up @@ -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:
Expand Down
30 changes: 18 additions & 12 deletions app/auth/page.tsx
Original file line number Diff line number Diff line change
@@ -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();
Expand Down Expand Up @@ -46,23 +46,29 @@ function AuthContent() {
margin: 'auto',
}}
>
{/* Icon Chip */}
{/* Brand Logo Chip */}
<div
style={{
width: '56px',
height: '56px',
width: '64px',
height: '64px',
borderRadius: 'var(--radius-lg)',
background: 'linear-gradient(135deg, rgba(16, 185, 129, 0.2) 0%, rgba(6, 182, 212, 0.1) 100%)',
color: 'var(--emerald-400)',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
overflow: 'hidden',
margin: '0 auto 1.5rem',
boxShadow: '0 0 24px var(--emerald-glow)',
border: '1px solid rgba(16, 185, 129, 0.3)',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
}}
>
<KeyRound size={26} />
<Image
src="/contragit.png"
alt="ContraGit Logo"
width={64}
height={64}
priority
style={{ objectFit: 'cover' }}
/>
</div>

<h1 style={{ fontSize: '1.75rem', fontWeight: 800, letterSpacing: '-0.03em', marginBottom: '0.5rem' }}>
Expand Down
2 changes: 1 addition & 1 deletion app/components/HeatmapCanvas.tsx
Original file line number Diff line number Diff line change
@@ -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 {
Expand Down
15 changes: 11 additions & 4 deletions app/components/layout/AppHeader.tsx
Original file line number Diff line number Diff line change
@@ -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,
Expand All @@ -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';
Expand Down Expand Up @@ -80,7 +80,14 @@ export function AppHeader({ user, activeNav = 'dashboard', defaultMobileOpen = f
aria-label="ContraGit Home"
>
<div className="app-header__logo" aria-hidden="true">
⚑
<Image
src="/contragit.png"
alt="ContraGit Logo"
width={36}
height={36}
priority
className="app-header__logo-img"
/>
</div>
<div className="app-header__title-group">
<span className="app-header__title">
Expand Down
3 changes: 1 addition & 2 deletions app/components/layout/MobileBottomBar.tsx
Original file line number Diff line number Diff line change
@@ -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 {
Expand Down
171 changes: 171 additions & 0 deletions app/components/studio/AmbientDeployIsland.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<div
role="region"
aria-label="Active Deployment Island"
style={{
position: 'fixed',
bottom: 'calc(1.25rem + env(safe-area-inset-bottom, 0px))',
right: '1.5rem',
zIndex: 999,
display: 'flex',
alignItems: 'center',
gap: '0.85rem',
padding: '0.65rem 1rem',
background: 'rgba(10, 14, 23, 0.88)',
backdropFilter: 'blur(20px) saturate(180%)',
WebkitBackdropFilter: 'blur(20px) saturate(180%)',
borderRadius: 'var(--radius-full)',
border: isCompleted
? '1px solid rgba(16, 185, 129, 0.5)'
: isPaused
? '1px solid rgba(245, 158, 11, 0.5)'
: '1px solid var(--border-active)',
boxShadow: isCompleted
? '0 8px 30px rgba(16, 185, 129, 0.25)'
: '0 8px 30px rgba(0, 0, 0, 0.65)',
cursor: 'pointer',
transition: 'all 0.2s var(--spring-ease)',
maxWidth: 'calc(100vw - 3rem)',
}}
onClick={scrollToHub}
>
{/* Icon Indicator */}
<div style={{ display: 'flex', alignItems: 'center' }}>
{isCompleted ? (
<CheckCircle2 size={18} color="var(--emerald-400)" />
) : isPaused ? (
<Pause size={18} color="#f59e0b" />
) : (
<Loader2 className="animate-spin" size={18} color="var(--emerald-400)" />
)}
</div>

{/* Main Info */}
<div style={{ display: 'flex', flexDirection: 'column', gap: '0.2rem' }}>
<div style={{ display: 'flex', alignItems: 'center', gap: '0.5rem', fontSize: '0.8rem' }}>
<span style={{ fontWeight: 700, color: 'var(--text-primary)' }}>
{isCompleted ? 'Deployment Complete' : `${job.completed} / ${job.total} commits`}
</span>
<span className="mono" style={{ color: 'var(--emerald-400)', fontWeight: 600 }}>
({progressPercent}%)
</span>
</div>
<div style={{ fontSize: '0.7rem', color: 'var(--text-muted)' }}>
<code className="mono" style={{ color: 'var(--text-secondary)' }}>{selectedRepo}:{branch}</code>
</div>
</div>

{/* Controls or Deep Link */}
<div style={{ display: 'flex', alignItems: 'center', gap: '0.35rem', marginLeft: '0.35rem' }} onClick={(e) => e.stopPropagation()}>
{isCompleted ? (
<a
href={`https://github.com/${repoOwner}/${selectedRepo}/commits/${branch}`}
target="_blank"
rel="noreferrer"
className="btn btn-ghost btn-sm"
style={{ padding: '0.25rem 0.5rem', fontSize: '0.75rem', height: '28px' }}
>
GitHub <ExternalLink size={12} />
</a>
) : isPaused ? (
<button
type="button"
onClick={onResume}
className="btn btn-primary btn-sm"
style={{ padding: '0.25rem 0.6rem', fontSize: '0.75rem', height: '28px' }}
title="Resume generation"
>
<Play size={12} />
</button>
) : (
<button
type="button"
onClick={onPause}
className="btn btn-secondary btn-sm"
style={{ padding: '0.25rem 0.6rem', fontSize: '0.75rem', height: '28px' }}
title="Pause generation"
>
<Pause size={12} />
</button>
)}
<button
type="button"
onClick={scrollToHub}
className="btn btn-ghost btn-sm"
style={{ padding: '0.25rem', height: '28px', width: '28px' }}
title="Scroll to Deployment Hub"
>
<ChevronUp size={14} />
</button>
</div>
</div>
);
}
Loading
Loading