Compare commits

..

No commits in common. "main" and "v1.0.0" have entirely different histories.
main ... v1.0.0

9 changed files with 2 additions and 3578 deletions

View File

@ -1,25 +0,0 @@
# ==============================================================================
# FlowGit — GitLab Pages Deployment Configuration
# Ultra-lightweight static deploy (< 15 MB RAM, < 0.1 CPU, execution time ~ 2s)
# ==============================================================================
# Minimal base image (~3.5 MB compressed). Ideal for tight memory/CPU constraints.
image: alpine:latest
stages:
- deploy
# The job name MUST be 'pages' for GitLab to recognize and publish GitLab Pages.
pages:
stage: deploy
script:
- rm -rf public
- mkdir -p public
- cp -r website/* public/
artifacts:
paths:
- public
expire_in: 30 days
rules:
# Trigger deployment only on pushes to the default branch (main/master)
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH

View File

@ -5,12 +5,6 @@
---
### Repositories & Downloads
- **GitLab (Recommended / Primary):** [https://kazaamo.space/djjdjdbf9/flowgit](https://kazaamo.space/djjdjdbf9/flowgit) &bull; [v1.0.0 Release](https://kazaamo.space/djjdjdbf9/flowgit/-/releases/v1.0.0)
- **GitHub (Backup Mirror):** [https://github.com/djjdjdbf9/flowgit](https://github.com/djjdjdbf9/flowgit) &bull; [Latest Release](https://github.com/djjdjdbf9/flowgit/releases/latest)
---
## Overview
**FlowGit** is a high-performance Windows Git desktop client designed for developers who demand both technical precision and visual elegance.
@ -101,11 +95,7 @@ Before building FlowGit from source, ensure you have:
### 1. Clone the Repository
```bash
# Recommended (GitLab):
git clone https://kazaamo.space/djjdjdbf9/flowgit.git
# Or Backup Mirror (GitHub):
git clone https://github.com/djjdjdbf9/flowgit.git
git clone <repository-url>
cd FlowGit
```
@ -156,14 +146,7 @@ The compiled executable will be located at:
release\win-unpacked\FlowGit.exe
```
### 5. Compile a portable windows executable
```cmd
npx electron-builder --win portable
```
The icon you can change with Resource Hacker
### 6. Generate Windows NSIS Installer & Portable Builds
### 5. Generate Windows NSIS Installer & Portable Builds
Builds the complete distribution installer (`FlowGit Setup.exe`) and portable executable:
```cmd
npm run dist

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.1 KiB

View File

@ -1,94 +0,0 @@
# FlowGit — Landing Page
> **A terminal-grade, single-page landing site for FlowGit**, crafted strictly according to the **Axiom Design System** (`DESIGN.md`) and the technical core documented in `README.md`.
- **GitLab (Recommended / Primary):** [https://kazaamo.space/djjdjdbf9/flowgit](https://kazaamo.space/djjdjdbf9/flowgit)
- **GitHub (Backup Mirror):** [https://github.com/djjdjdbf9/flowgit](https://github.com/djjdjdbf9/flowgit)
---
## ⚡ Quick Preview
You can view the site immediately without installing heavy dependencies:
### Option A: Direct Browser Open
Double-click `index.html` or open it with your browser:
```cmd
start index.html
```
### Option B: Local Web Server
If you prefer running a local HTTP server:
```cmd
npx serve . -l 3000
```
Then navigate to `http://localhost:3000`.
---
## 🎨 Axiom Design System Compliance
This site strictly follows the tokens and visual rules specified in `DESIGN.md`:
| Token / Spec | Value | Implementation |
|---|---|---|
| **Palette Canvas** | `#000000` (Void) | Main page background, nav surface, terminal canvas |
| **Primary Surface** | `#111111` (Carbon) | Feature card surfaces, app chrome, code containers |
| **Elevated Surface**| `#191919` (Graphite) | Case study cards, product mock sub-panels |
| **Hairline Dividers**| `#202020` (Iron) | Structural hairline borders & row dividers |
| **Secondary Borders**| `#3a3a3a` (Slate) | Interactive controls, ghost buttons |
| **Primary Accent** | `#da5c2c` (Ember) | Action CTAs, active tab underlines, graph HEAD highlights, and the 2px left border on case-study cards |
| **Typography** | BerkeleyMono / JetBrains Mono | Universal monospace grid across headers, body, nav, buttons, and tables |
| **Geometry** | `2px` corners | Applied to cards, buttons, inputs, panels. `9999px` strictly for status pills |
| **Shadows & Gradients**| Flat / None | No soft blur shadows, no neon gradients, zero decorative slop |
---
## 🚀 Key Interactive Features
1. **Live Blueprint DAG Graph Engine (SVG)**:
- Interactive directed acyclic graph with topological commit ancestry.
- Panning (drag canvas) & smooth mouse-wheel zooming (0.4x to 2.5x).
- Parent-child cubic Bezier connector splines with branch color indicators.
- Live Minimap Radar widget synced with viewport coordinates.
- Click any commit node to slide open the **Commit Inspector Drawer** with file diff stats.
- Toolbar with live commit search filter, zoom-to-fit, and jump-to-HEAD.
2. **Porcelain-v1 Staging Simulator**:
- Interactive Staged / Unstaged / Untracked file matrix.
- Stage, unstage, or batch-stage files with immediate index feedback.
- Commit message input with live 72-character limit counter.
3. **Advanced Diff Viewer**:
- Interactive toggle between **Unified** and **Side-by-Side (Split)** diff views.
- Syntax-highlighted additions (`+`), deletions (`-`), and folded hunk headers.
4. **Branch Matrix & Stashes**:
- Local/remote branch tracking with `↑ ahead / ↓ behind` upstream indicators.
- Stash list with one-click action feedback.
5. **Terminal Code Tabs with One-Click Copy**:
- `Portable (.exe)`, `Compiled Binaries (.zip)`, and `source build` instructions.
- Instant clipboard copy with monospaced toast confirmation.
6. **Global Keyboard Shortcuts**:
- `1` — Switch to Blueprint Graph tab
- `2` — Switch to Porcelain Staging tab
- `3` — Switch to Diff Viewer tab
- `4` — Switch to Branch Matrix tab
- `H` — Jump to HEAD commit in DAG
- `F` — Zoom-to-fit canvas
- `Esc` — Close Commit Inspector drawer
---
## 📁 File Structure
```
website/
├── index.html # Semantic single-page HTML5 structure
├── styles.css # Authoritative Axiom design tokens & layout
├── app.js # Interactive DAG engine, tabs, diff toggle, staging
├── package.json # Optional local preview runner
└── README.md # Documentation and design specifications
```

View File

@ -1,903 +0,0 @@
/**
* FlowGit Landing Page Interactive Logic
* Axiom Design System & Blueprint DAG Engine
*/
(function () {
'use strict';
// ── Global State ──
const state = {
activeTab: 'graph',
zoom: 1,
pan: { x: 40, y: 40 },
isDragging: false,
dragStart: { x: 0, y: 0 },
selectedNodeId: 'c1',
diffMode: 'unified',
commandTab: 'portable',
stagedFiles: [
{ id: 'f1', name: 'electron/git/executor.ts', status: 'M', additions: 34, deletions: 2 },
{ id: 'f2', name: 'src/views/Repository/Graph/GraphView.tsx', status: 'A', additions: 188, deletions: 0 },
],
unstagedFiles: [
{ id: 'f3', name: 'shared/types/git.types.ts', status: 'M', additions: 12, deletions: 4 },
{ id: 'f4', name: 'DESIGN.md', status: 'M', additions: 6, deletions: 1 },
{ id: 'f5', name: 'tests/git/executor.test.ts', status: 'U', additions: 45, deletions: 0 },
]
};
// ── Blueprint Commit Graph Topology ──
const graphNodes = [
{
id: 'c1',
hash: 'a94f1c0',
shortHash: 'a94f1c0',
branch: 'main',
isHead: true,
tags: ['HEAD -> main', 'origin/main'],
color: '#da5c2c',
x: 640,
y: 100,
author: 'Alex Vance <alex@flowgit.io>',
date: '2026-10-25 22:41:18',
message: 'feat(graph): topological DAG bezier connector & minimap sync',
parentIds: ['c2', 'c3'],
files: [
{ path: 'src/views/Repository/Graph/GraphView.tsx', status: 'M', adds: 84, dels: 12 },
{ path: 'electron/git/history.ts', status: 'M', adds: 42, dels: 5 }
],
diffSnippet: `@@ -118,9 +118,15 @@ export function computeBezierCurves(nodes, edges) {
- const path = \`M \${source.x} \${source.y} L \${target.x} \${target.y}\`;
+ const dx = Math.abs(target.x - source.x) * 0.5;
+ const cp1x = source.x + dx;
+ const cp2x = target.x - dx;
+ const path = \`M \${source.x} \${source.y} C \${cp1x} \${source.y}, \${cp2x} \${target.y}, \${target.x} \${target.y}\`;
+ return { id: \`\${source.id}-\${target.id}\`, d: path, color: edgeColor };
}`
},
{
id: 'c2',
hash: '7b3e9a1',
shortHash: '7b3e9a1',
branch: 'main',
isHead: false,
tags: [],
color: '#da5c2c',
x: 360,
y: 100,
author: 'Alex Vance <alex@flowgit.io>',
date: '2026-10-25 19:14:02',
message: 'perf(sqlite): WAL mode persistence for 100k+ commit repositories',
parentIds: ['c4'],
files: [
{ path: 'electron/persistence/database.ts', status: 'M', adds: 26, dels: 3 }
],
diffSnippet: `@@ -42,3 +42,7 @@ export function initDatabase(dbPath: string) {
+ db.pragma('journal_mode = WAL');
+ db.pragma('synchronous = NORMAL');
+ db.pragma('cache_size = -64000'); // 64MB memory cache`
},
{
id: 'c3',
hash: 'f419c82',
shortHash: 'f419c82',
branch: 'feature/blueprint-minimap',
isHead: false,
tags: ['feat/minimap'],
color: '#38bdf8',
x: 360,
y: 260,
author: 'Elena Rostova <elena@flowgit.io>',
date: '2026-10-25 18:02:40',
message: 'feat(minimap): radar viewport bounding box with pan listener',
parentIds: ['c4'],
files: [
{ path: 'src/components/MinimapRadar.tsx', status: 'A', adds: 112, dels: 0 }
],
diffSnippet: `@@ -0,0 +1,18 @@
+export const MinimapRadar: React.FC<MinimapProps> = ({ bounds, viewport }) => {
+ const ratioX = 140 / bounds.width;
+ const ratioY = 90 / bounds.height;
+ return <div className="minimap-box" style={{ left: viewport.x * ratioX }} />;
+};`
},
{
id: 'c4',
hash: 'd28a011',
shortHash: 'd28a011',
branch: 'main',
isHead: false,
tags: ['v1.0.0-rc.2'],
color: '#da5c2c',
x: 80,
y: 100,
author: 'Alex Vance <alex@flowgit.io>',
date: '2026-10-24 23:55:10',
message: 'security(executor): enforce argv array spawning with safe env redaction',
parentIds: [],
files: [
{ path: 'electron/git/executor.ts', status: 'M', adds: 65, dels: 18 }
],
diffSnippet: `@@ -15,7 +15,9 @@ export function executeGit(args: string[], cwd: string) {
- // DEPRECATED: exec(\`git \${args.join(' ')}\`)
+ // Safe argv spawn — no shell string interpolation
+ return spawn(gitPath, args, { cwd, shell: false, env: sanitizedEnv });
}`
}
];
// ── Initialization ──
document.addEventListener('DOMContentLoaded', () => {
initAnnouncement();
initTabs();
initBlueprintGraph();
initStagingDemo();
initDiffViewer();
initHistogram();
initTerminalTabs();
initClipboardCopy();
initKeyboardShortcuts();
initRepoLinks();
updateDiffDisplay();
});
// ── 1. Announcement Banner ──
function initAnnouncement() {
const banner = document.getElementById('announcementBanner');
const closeBtn = document.getElementById('closeAnnouncement');
if (!banner || !closeBtn) return;
if (sessionStorage.getItem('flowgit_announcement_closed') === 'true') {
banner.style.display = 'none';
}
closeBtn.addEventListener('click', () => {
banner.style.display = 'none';
sessionStorage.setItem('flowgit_announcement_closed', 'true');
});
}
// ── 2. Showcase Navigation Tabs ──
function initTabs() {
const tabs = document.querySelectorAll('.product-tab');
const panels = document.querySelectorAll('.view-panel');
tabs.forEach((tab) => {
tab.addEventListener('click', () => {
const target = tab.getAttribute('data-tab');
if (!target) return;
tabs.forEach((t) => t.classList.remove('active'));
panels.forEach((p) => p.classList.remove('active'));
tab.classList.add('active');
const activePanel = document.getElementById(`panel-${target}`);
if (activePanel) {
activePanel.classList.add('active');
}
state.activeTab = target;
// If graph tab opened, recalculate center
if (target === 'graph') {
renderGraph();
}
});
});
}
// ── 3. Blueprint DAG SVG Engine ──
function initBlueprintGraph() {
const container = document.getElementById('graphCanvasContainer');
const svg = document.getElementById('graphSvg');
if (!container || !svg) return;
// Zoom Controls
const zoomInBtn = document.getElementById('zoomInBtn');
const zoomOutBtn = document.getElementById('zoomOutBtn');
const zoomFitBtn = document.getElementById('zoomFitBtn');
const jumpHeadBtn = document.getElementById('jumpHeadBtn');
const searchInput = document.getElementById('graphSearchInput');
if (zoomInBtn) {
zoomInBtn.addEventListener('click', () => {
state.zoom = Math.min(2.5, state.zoom + 0.15);
renderGraph();
});
}
if (zoomOutBtn) {
zoomOutBtn.addEventListener('click', () => {
state.zoom = Math.max(0.4, state.zoom - 0.15);
renderGraph();
});
}
if (zoomFitBtn) {
zoomFitBtn.addEventListener('click', () => {
fitGraphToView();
});
}
if (jumpHeadBtn) {
jumpHeadBtn.addEventListener('click', () => {
jumpToHead();
});
}
if (searchInput) {
searchInput.addEventListener('input', (e) => {
const query = e.target.value.toLowerCase().trim();
filterGraphNodes(query);
});
}
// Mouse Pan & Drag
container.addEventListener('mousedown', (e) => {
// Ignore click on node cards or controls
if (e.target.closest('.node-card-group') || e.target.closest('.graph-controls-toolbar')) {
return;
}
state.isDragging = true;
state.dragStart = { x: e.clientX - state.pan.x, y: e.clientY - state.pan.y };
container.style.cursor = 'grabbing';
});
window.addEventListener('mousemove', (e) => {
if (!state.isDragging) return;
state.pan.x = e.clientX - state.dragStart.x;
state.pan.y = e.clientY - state.dragStart.y;
renderGraph();
});
window.addEventListener('mouseup', () => {
if (state.isDragging) {
state.isDragging = false;
container.style.cursor = 'grab';
}
});
// Mouse wheel zoom
container.addEventListener('wheel', (e) => {
e.preventDefault();
const zoomFactor = e.deltaY < 0 ? 1.08 : 0.92;
const newZoom = Math.max(0.4, Math.min(2.5, state.zoom * zoomFactor));
// Zoom centered on cursor
const rect = container.getBoundingClientRect();
const mouseX = e.clientX - rect.left;
const mouseY = e.clientY - rect.top;
state.pan.x = mouseX - (mouseX - state.pan.x) * (newZoom / state.zoom);
state.pan.y = mouseY - (mouseY - state.pan.y) * (newZoom / state.zoom);
state.zoom = newZoom;
renderGraph();
}, { passive: false });
// Close Inspector button
const closeInspectorBtn = document.getElementById('closeInspectorBtn');
if (closeInspectorBtn) {
closeInspectorBtn.addEventListener('click', () => {
const drawer = document.getElementById('commitInspector');
if (drawer) drawer.classList.remove('open');
});
}
renderGraph();
}
function renderGraph() {
const svg = document.getElementById('graphSvg');
if (!svg) return;
const NODE_WIDTH = 230;
const NODE_HEIGHT = 76;
// Clear previous SVG content
svg.innerHTML = '';
// Create main viewport transformation group
const gViewport = document.createElementNS('http://www.w3.org/2000/svg', 'g');
gViewport.setAttribute('transform', `translate(${state.pan.x}, ${state.pan.y}) scale(${state.zoom})`);
// 1. Draw Bezier Connector Edges
graphNodes.forEach((node) => {
node.parentIds.forEach((parentId) => {
const parent = graphNodes.find((n) => n.id === parentId);
if (!parent) return;
// Source = parent right edge; Target = child left edge
const startX = parent.x + NODE_WIDTH;
const startY = parent.y + NODE_HEIGHT / 2;
const endX = node.x;
const endY = node.y + NODE_HEIGHT / 2;
const dx = Math.max(40, Math.abs(endX - startX) * 0.5);
const cp1x = startX + dx;
const cp1y = startY;
const cp2x = endX - dx;
const cp2y = endY;
const pathData = `M ${startX} ${startY} C ${cp1x} ${cp1y}, ${cp2x} ${cp2y}, ${endX} ${endY}`;
const path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
path.setAttribute('d', pathData);
path.setAttribute('class', 'dag-edge');
path.setAttribute('stroke', node.color);
path.setAttribute('stroke-width', '2');
path.setAttribute('stroke-opacity', '0.7');
gViewport.appendChild(path);
});
});
// 2. Draw Commit Nodes
graphNodes.forEach((node) => {
const isSelected = state.selectedNodeId === node.id;
const group = document.createElementNS('http://www.w3.org/2000/svg', 'g');
group.setAttribute('class', `node-card-group ${isSelected ? 'selected' : ''}`);
group.setAttribute('data-node-id', node.id);
group.setAttribute('transform', `translate(${node.x}, ${node.y})`);
group.style.cursor = 'pointer';
// Base card rectangle
const rect = document.createElementNS('http://www.w3.org/2000/svg', 'rect');
rect.setAttribute('width', NODE_WIDTH);
rect.setAttribute('height', NODE_HEIGHT);
rect.setAttribute('rx', '2');
rect.setAttribute('fill', '#111111');
rect.setAttribute('stroke', isSelected ? '#da5c2c' : '#202020');
rect.setAttribute('stroke-width', isSelected ? '2' : '1');
group.appendChild(rect);
// Branch color indicator stripe on left edge (2px)
const stripe = document.createElementNS('http://www.w3.org/2000/svg', 'rect');
stripe.setAttribute('x', '0');
stripe.setAttribute('y', '0');
stripe.setAttribute('width', '3');
stripe.setAttribute('height', NODE_HEIGHT);
stripe.setAttribute('rx', '1');
stripe.setAttribute('fill', node.color);
group.appendChild(stripe);
// Short Hash Tag Pill
const hashText = document.createElementNS('http://www.w3.org/2000/svg', 'text');
hashText.setAttribute('x', '12');
hashText.setAttribute('y', '22');
hashText.setAttribute('fill', '#eeeeee');
hashText.setAttribute('font-family', 'var(--font-mono)');
hashText.setAttribute('font-size', '12');
hashText.setAttribute('font-weight', '700');
hashText.textContent = node.shortHash;
group.appendChild(hashText);
// Branch / Tag Badge (if any)
if (node.tags.length > 0) {
const badgeBg = document.createElementNS('http://www.w3.org/2000/svg', 'rect');
badgeBg.setAttribute('x', '76');
badgeBg.setAttribute('y', '10');
badgeBg.setAttribute('width', '140');
badgeBg.setAttribute('height', '16');
badgeBg.setAttribute('rx', '2');
badgeBg.setAttribute('fill', '#191919');
badgeBg.setAttribute('stroke', '#3a3a3a');
group.appendChild(badgeBg);
const badgeText = document.createElementNS('http://www.w3.org/2000/svg', 'text');
badgeText.setAttribute('x', '82');
badgeText.setAttribute('y', '22');
badgeText.setAttribute('fill', node.color);
badgeText.setAttribute('font-family', 'var(--font-mono)');
badgeText.setAttribute('font-size', '10');
badgeText.setAttribute('font-weight', '700');
badgeText.textContent = node.tags.join(', ').substring(0, 22);
group.appendChild(badgeText);
}
// Commit Message
const msgText = document.createElementNS('http://www.w3.org/2000/svg', 'text');
msgText.setAttribute('x', '12');
msgText.setAttribute('y', '44');
msgText.setAttribute('fill', '#b4b4b4');
msgText.setAttribute('font-family', 'var(--font-mono)');
msgText.setAttribute('font-size', '11');
const truncated = node.message.length > 28 ? node.message.substring(0, 27) + '…' : node.message;
msgText.textContent = truncated;
group.appendChild(msgText);
// Author & Time Footer
const authorText = document.createElementNS('http://www.w3.org/2000/svg', 'text');
authorText.setAttribute('x', '12');
authorText.setAttribute('y', '62');
authorText.setAttribute('fill', '#606060');
authorText.setAttribute('font-family', 'var(--font-mono)');
authorText.setAttribute('font-size', '10');
authorText.textContent = node.author.split('<')[0].trim() + ' • ' + node.date.split(' ')[0];
group.appendChild(authorText);
// Click event on node
group.addEventListener('click', (e) => {
e.stopPropagation();
selectNode(node);
});
gViewport.appendChild(group);
});
svg.appendChild(gViewport);
updateMinimap();
}
function selectNode(node) {
state.selectedNodeId = node.id;
renderGraph();
// Populate Inspector Drawer
const drawer = document.getElementById('commitInspector');
if (!drawer) return;
document.getElementById('inspHash').textContent = node.hash;
document.getElementById('inspAuthor').textContent = node.author;
document.getElementById('inspDate').textContent = node.date;
document.getElementById('inspMessage').textContent = node.message;
// Populate files
const fileListEl = document.getElementById('inspFileList');
if (fileListEl) {
fileListEl.innerHTML = node.files
.map(
(f) => `
<div style="display:flex; justify-content:space-between; margin-bottom:4px; font-size:11px;">
<span style="color:#eeeeee;">${f.path}</span>
<span style="color:#4ade80;">+${f.adds} <span style="color:#f87171;">-${f.dels}</span></span>
</div>
`
)
.join('');
}
const diffPreviewEl = document.getElementById('inspDiffCode');
if (diffPreviewEl) {
diffPreviewEl.textContent = node.diffSnippet;
}
drawer.classList.add('open');
}
function jumpToHead() {
const headNode = graphNodes.find((n) => n.isHead);
if (!headNode) return;
const container = document.getElementById('graphCanvasContainer');
if (!container) return;
const w = container.clientWidth;
const h = container.clientHeight;
state.pan.x = w / 2 - (headNode.x + 115) * state.zoom;
state.pan.y = h / 2 - (headNode.y + 38) * state.zoom;
renderGraph();
}
function fitGraphToView() {
const container = document.getElementById('graphCanvasContainer');
if (!container) return;
state.zoom = 0.9;
state.pan.x = 40;
state.pan.y = 80;
renderGraph();
}
function filterGraphNodes(query) {
const groups = document.querySelectorAll('.node-card-group');
groups.forEach((g) => {
const nodeId = g.getAttribute('data-node-id');
const node = graphNodes.find((n) => n.id === nodeId);
if (!node) return;
if (!query || node.message.toLowerCase().includes(query) || node.hash.includes(query)) {
g.style.opacity = '1';
} else {
g.style.opacity = '0.25';
}
});
}
function updateMinimap() {
const box = document.getElementById('minimapBox');
if (!box) return;
// Compute relative viewport position
const normX = Math.max(0, Math.min(100, (-state.pan.x / 10) + 20));
const normY = Math.max(0, Math.min(60, (-state.pan.y / 10) + 10));
box.style.left = `${normX}px`;
box.style.top = `${normY}px`;
box.style.width = `${Math.max(30, 80 / state.zoom)}px`;
box.style.height = `${Math.max(20, 50 / state.zoom)}px`;
}
// ── 4. Interactive Staging Demo (Tab 2) ──
function initStagingDemo() {
renderStagingLists();
const commitInput = document.getElementById('commitInputMsg');
const charCounter = document.getElementById('commitCharCounter');
const commitBtn = document.getElementById('commitBtn');
if (commitInput && charCounter) {
commitInput.addEventListener('input', () => {
const len = commitInput.value.length;
charCounter.textContent = `${len} / 72 chars`;
if (len > 72) {
charCounter.style.color = '#ef4444';
} else {
charCounter.style.color = '#606060';
}
});
}
if (commitBtn && commitInput) {
commitBtn.addEventListener('click', () => {
if (!commitInput.value.trim()) {
showToast('Please enter a commit message');
return;
}
if (state.stagedFiles.length === 0) {
showToast('No changes staged to commit');
return;
}
const msg = commitInput.value.trim();
showToast(`[OK] Committed: "${msg.substring(0, 30)}..."`);
state.stagedFiles = [];
commitInput.value = '';
if (charCounter) charCounter.textContent = '0 / 72 chars';
renderStagingLists();
});
}
const stageAllBtn = document.getElementById('stageAllBtn');
if (stageAllBtn) {
stageAllBtn.addEventListener('click', () => {
state.stagedFiles = [...state.stagedFiles, ...state.unstagedFiles];
state.unstagedFiles = [];
renderStagingLists();
showToast('All files staged (porcelain index updated)');
});
}
const unstageAllBtn = document.getElementById('unstageAllBtn');
if (unstageAllBtn) {
unstageAllBtn.addEventListener('click', () => {
state.unstagedFiles = [...state.unstagedFiles, ...state.stagedFiles];
state.stagedFiles = [];
renderStagingLists();
showToast('All files unstaged');
});
}
}
function renderStagingLists() {
const stagedEl = document.getElementById('stagedFilesContainer');
const unstagedEl = document.getElementById('unstagedFilesContainer');
const stagedCountEl = document.getElementById('stagedCount');
const unstagedCountEl = document.getElementById('unstagedCount');
if (stagedCountEl) stagedCountEl.textContent = `(${state.stagedFiles.length})`;
if (unstagedCountEl) unstagedCountEl.textContent = `(${state.unstagedFiles.length})`;
if (stagedEl) {
stagedEl.innerHTML = state.stagedFiles.length === 0
? '<div style="padding:12px; color:#606060; font-size:11px;">No files staged in index.</div>'
: state.stagedFiles.map((file) => `
<div class="staging-file-row">
<div class="staging-file-name">
<span class="file-status-letter status-${file.status.toLowerCase()}">${file.status}</span>
<span>${file.name}</span>
</div>
<button class="staging-action-btn" data-action="unstage" data-id="${file.id}">Unstage -</button>
</div>
`).join('');
}
if (unstagedEl) {
unstagedEl.innerHTML = state.unstagedFiles.length === 0
? '<div style="padding:12px; color:#606060; font-size:11px;">Working tree clean.</div>'
: state.unstagedFiles.map((file) => `
<div class="staging-file-row">
<div class="staging-file-name">
<span class="file-status-letter status-${file.status.toLowerCase()}">${file.status}</span>
<span>${file.name}</span>
</div>
<button class="staging-action-btn" data-action="stage" data-id="${file.id}">Stage +</button>
</div>
`).join('');
}
// Attach click handlers
document.querySelectorAll('.staging-action-btn').forEach((btn) => {
btn.addEventListener('click', (e) => {
e.stopPropagation();
const action = btn.getAttribute('data-action');
const id = btn.getAttribute('data-id');
if (action === 'stage') {
const item = state.unstagedFiles.find((f) => f.id === id);
if (item) {
state.unstagedFiles = state.unstagedFiles.filter((f) => f.id !== id);
state.stagedFiles.push(item);
}
} else if (action === 'unstage') {
const item = state.stagedFiles.find((f) => f.id === id);
if (item) {
state.stagedFiles = state.stagedFiles.filter((f) => f.id !== id);
state.unstagedFiles.push(item);
}
}
renderStagingLists();
});
});
}
// ── 5. Advanced Diff Viewer (Tab 3) ──
function initDiffViewer() {
const unifiedBtn = document.getElementById('diffUnifiedBtn');
const splitBtn = document.getElementById('diffSplitBtn');
if (unifiedBtn && splitBtn) {
unifiedBtn.addEventListener('click', () => {
state.diffMode = 'unified';
unifiedBtn.classList.add('active');
splitBtn.classList.remove('active');
updateDiffDisplay();
});
splitBtn.addEventListener('click', () => {
state.diffMode = 'split';
splitBtn.classList.add('active');
unifiedBtn.classList.remove('active');
updateDiffDisplay();
});
}
}
function updateDiffDisplay() {
const diffContainer = document.getElementById('diffLinesContainer');
if (!diffContainer) return;
const diffLines = [
{ type: 'header', text: '@@ -45,14 +45,21 @@ export async function executeGitSafe(args, options) {' },
{ type: 'context', oldNum: '45', newNum: '45', text: ' const binaryPath = await detectSystemGitBinary();' },
{ type: 'context', oldNum: '46', newNum: '46', text: ' if (!binaryPath) {' },
{ type: 'context', oldNum: '47', newNum: '47', text: ' throw new GitBinaryNotFoundError();' },
{ type: 'context', oldNum: '48', newNum: '48', text: ' }' },
{ type: 'deleted', oldNum: '49', newNum: '', text: '- const rawOutput = child_process.execSync(`git ${args.join(" ")}`);' },
{ type: 'added', oldNum: '', newNum: '49', text: '+ // SECURITY: Spawn directly with argv arguments array (no shell concatenation)' },
{ type: 'added', oldNum: '', newNum: '50', text: '+ const sanitizedArgs = redactAuthenticationTokens(args);' },
{ type: 'added', oldNum: '', newNum: '51', text: '+ const child = child_process.spawn(binaryPath, sanitizedArgs, {' },
{ type: 'added', oldNum: '', newNum: '52', text: '+ cwd: options.workingDirectory,' },
{ type: 'added', oldNum: '', newNum: '53', text: '+ shell: false,' },
{ type: 'added', oldNum: '', newNum: '54', text: '+ windowsHide: true,' },
{ type: 'added', oldNum: '', newNum: '55', text: '+ });' },
{ type: 'context', oldNum: '50', newNum: '56', text: ' return parsePorcelainOutput(child);' },
{ type: 'context', oldNum: '51', newNum: '57', text: '}' }
];
if (state.diffMode === 'unified') {
diffContainer.innerHTML = diffLines
.map((line) => {
if (line.type === 'header') {
return `<div class="diff-line hunk-header"><div class="diff-num-col">...</div><div class="diff-text-col">${line.text}</div></div>`;
}
const cls = line.type === 'added' ? 'added' : line.type === 'deleted' ? 'deleted' : '';
return `
<div class="diff-line ${cls}">
<div class="diff-num-col">${line.oldNum || ''}</div>
<div class="diff-num-col" style="border-right: 1px solid var(--color-slate);">${line.newNum || ''}</div>
<div class="diff-text-col">${escapeHtml(line.text)}</div>
</div>
`;
})
.join('');
} else {
// Split mode simulation
diffContainer.innerHTML = `
<div style="display:grid; grid-template-columns: 1fr 1fr; height: 100%;">
<div style="border-right: 1px solid var(--color-iron);">
<div style="padding:6px 12px; background:#111; font-size:11px; color:#f87171; border-bottom:1px solid #202020;">BASE (REVISION A)</div>
<div class="diff-line"><div class="diff-num-col">45</div><div class="diff-text-col"> const binaryPath = await detectSystemGitBinary();</div></div>
<div class="diff-line"><div class="diff-num-col">46</div><div class="diff-text-col"> if (!binaryPath) {</div></div>
<div class="diff-line"><div class="diff-num-col">47</div><div class="diff-text-col"> throw new GitBinaryNotFoundError();</div></div>
<div class="diff-line"><div class="diff-num-col">48</div><div class="diff-text-col"> }</div></div>
<div class="diff-line deleted"><div class="diff-num-col">49</div><div class="diff-text-col">- const rawOutput = child_process.execSync(...);</div></div>
<div class="diff-line"><div class="diff-num-col">50</div><div class="diff-text-col"> return parsePorcelainOutput(child);</div></div>
</div>
<div>
<div style="padding:6px 12px; background:#111; font-size:11px; color:#4ade80; border-bottom:1px solid #202020;">HEAD (REVISION B)</div>
<div class="diff-line"><div class="diff-num-col">45</div><div class="diff-text-col"> const binaryPath = await detectSystemGitBinary();</div></div>
<div class="diff-line"><div class="diff-num-col">46</div><div class="diff-text-col"> if (!binaryPath) {</div></div>
<div class="diff-line"><div class="diff-num-col">47</div><div class="diff-text-col"> throw new GitBinaryNotFoundError();</div></div>
<div class="diff-line"><div class="diff-num-col">48</div><div class="diff-text-col"> }</div></div>
<div class="diff-line added"><div class="diff-num-col">49</div><div class="diff-text-col">+ const child = child_process.spawn(binaryPath...);</div></div>
<div class="diff-line added"><div class="diff-num-col">50</div><div class="diff-text-col">+ return parsePorcelainOutput(child);</div></div>
</div>
</div>
`;
}
}
// ── 6. Activity Histogram (Axiom Design Specification) ──
function initHistogram() {
const container = document.getElementById('histogramBars');
if (!container) return;
// Pattern of 48 activity bars
const heights = [
4, 6, 8, 4, 12, 16, 8, 4, 14, 18, 16, 20, 10, 6, 4, 8, 14, 16, 12, 18,
22, 14, 10, 8, 4, 12, 16, 18, 20, 24, 16, 12, 8, 14, 18, 16, 20, 24, 28,
18, 14, 10, 6, 12, 16, 20, 24, 18
];
container.innerHTML = heights
.map((h, i) => {
const isAccent = h >= 22;
return `<div class="histogram-bar ${isAccent ? 'active-accent' : ''}" style="height:${h}px;" title="Activity slot ${i + 1}: ${h * 3} events"></div>`;
})
.join('');
}
// ── 7. Interactive Terminal Commands Switcher ──
function initTerminalTabs() {
const tabs = document.querySelectorAll('.terminal-tab-btn');
const codeBlock = document.getElementById('terminalCodeDisplay');
if (!tabs || !codeBlock) return;
const commands = {
portable: `# Standalone Single-File Executable
# Download FlowGit-Portable.exe from GitLab (Recommended) or GitHub Mirror:
.\\FlowGit-Portable.exe
# Zero installation, zero registry writes`,
binaries: `# Compiled Binaries (Pre-compiled Unpacked Archive)
# 1. Download and extract FlowGit-win-x64-binaries.zip from GitLab / GitHub:
Expand-Archive -Path .\\FlowGit-win-x64-binaries.zip -DestinationPath .\\FlowGit
# 2. Run the executable directly (no installer required):
.\\FlowGit\\FlowGit.exe`,
source: `# 1. Clone repository from GitLab (or GitHub Mirror)
git clone https://kazaamo.space/djjdjdbf9/flowgit.git
cd flowgit
# 2. Install dependencies & run dev server
npm install
npm run dev
# 3. Compile standalone Windows directory or portable exe
npm run dist:dir
npx electron-builder --win portable`
};
tabs.forEach((tab) => {
tab.addEventListener('click', () => {
const type = tab.getAttribute('data-cmd');
if (!type || !commands[type]) return;
tabs.forEach((t) => t.classList.remove('active'));
tab.classList.add('active');
codeBlock.textContent = commands[type];
state.commandTab = type;
});
});
}
// ── 8. Clipboard Copy Functionality ──
function initClipboardCopy() {
const copyBtns = document.querySelectorAll('[data-copy-target]');
copyBtns.forEach((btn) => {
btn.addEventListener('click', () => {
const targetId = btn.getAttribute('data-copy-target');
const targetEl = document.getElementById(targetId);
if (!targetEl) return;
const text = targetEl.textContent || targetEl.innerText;
navigator.clipboard.writeText(text.trim()).then(() => {
const original = btn.textContent;
btn.textContent = '✓ COPIED';
showToast('Copied to clipboard');
setTimeout(() => {
btn.textContent = original;
}, 1800);
}).catch(() => {
showToast('Error copying to clipboard');
});
});
});
const copyFloatingBtn = document.getElementById('copyTerminalFloatingBtn');
if (copyFloatingBtn) {
copyFloatingBtn.addEventListener('click', () => {
const codeEl = document.getElementById('terminalCodeDisplay');
if (!codeEl) return;
navigator.clipboard.writeText(codeEl.textContent.trim()).then(() => {
copyFloatingBtn.textContent = '✓ COPIED';
showToast('Terminal command copied');
setTimeout(() => {
copyFloatingBtn.textContent = 'COPY';
}, 1800);
});
});
}
}
// ── 9. Global Keyboard Shortcuts ──
function initKeyboardShortcuts() {
window.addEventListener('keydown', (e) => {
// Don't trigger when typing in inputs or textareas
if (['INPUT', 'TEXTAREA'].includes(e.target.tagName)) {
return;
}
if (e.key === '1') {
switchTab('graph');
} else if (e.key === '2') {
switchTab('staging');
} else if (e.key === '3') {
switchTab('diff');
} else if (e.key === '4') {
switchTab('branches');
} else if (e.key === 'h' || e.key === 'H') {
jumpToHead();
showToast('Jumped to HEAD commit');
} else if (e.key === 'f' || e.key === 'F') {
fitGraphToView();
showToast('Fitted DAG graph to viewport');
} else if (e.key === 'Escape') {
const drawer = document.getElementById('commitInspector');
if (drawer) drawer.classList.remove('open');
}
});
}
function switchTab(tabId) {
const tabBtn = document.querySelector(`.product-tab[data-tab="${tabId}"]`);
if (tabBtn) tabBtn.click();
}
// ── 10. Repository Link Notification ──
function initRepoLinks() {
document.querySelectorAll('.github-repo-link').forEach((link) => {
link.addEventListener('click', (e) => {
const href = link.getAttribute('href');
if (!href || href === '#' || href === '#downloads') {
showToast('GitHub release repository link will be updated soon');
}
});
});
}
// ── Helpers ──
function showToast(message) {
const toast = document.getElementById('globalToast');
if (!toast) return;
toast.textContent = message;
toast.classList.add('show');
clearTimeout(toast._timeout);
toast._timeout = setTimeout(() => {
toast.classList.remove('show');
}, 2400);
}
function escapeHtml(str) {
return str
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;');
}
})();

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.1 KiB

View File

@ -1,768 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>FlowGit — Blueprint-Inspired Git Desktop Application for Windows</title>
<meta name="description" content="A high-performance, Blueprint-inspired Windows Git desktop client. Built with Electron 33, React 18, and the terminal-grade Axiom design system.">
<link rel="stylesheet" href="styles.css">
<link rel="icon" type="image/x-icon" href="favicon.ico">
</head>
<body>
<!-- ── Top Announcement Strip ── -->
<div class="announcement-banner" id="announcementBanner">
<div class="announcement-content">
<div class="announcement-badge">
<span class="pulse-dot"></span>
<span>v1.0.0-rc.3 RELEASE</span>
</div>
<div class="announcement-text">
<span>Topological DAG Engine, Electron 33 &amp; SQLite WAL persistence are now live.</span>
<a href="https://kazaamo.space/djjdjdbf9/flowgit/-/releases/v1.0.0" target="_blank" rel="noopener noreferrer" class="announcement-link">Get v1.0.0 on GitLab (Recommended) →</a>
</div>
<button class="announcement-close" id="closeAnnouncement" title="Dismiss announcement">×</button>
</div>
</div>
<!-- ── Navigation Bar ── -->
<header class="nav-bar">
<div class="container nav-container">
<div class="brand-cluster">
<a href="#" class="brand-logo">
<span class="brand-mark">▲</span>
<span>FlowGit</span>
</a>
<span class="brand-badge">WIN-X64</span>
</div>
<nav>
<ul class="nav-links">
<li><a href="#graph" class="nav-link"><span class="nav-index">01</span> GRAPH</a></li>
<li><a href="#staging" class="nav-link"><span class="nav-index">02</span> STAGING</a></li>
<li><a href="#diffs" class="nav-link"><span class="nav-index">03</span> DIFFS</a></li>
<li><a href="#features" class="nav-link"><span class="nav-index">04</span> FEATURES</a></li>
<li><a href="#architecture" class="nav-link"><span class="nav-index">05</span> ARCHITECTURE</a></li>
<li><a href="#downloads" class="nav-link"><span class="nav-index">06</span> DOWNLOAD</a></li>
</ul>
</nav>
<div class="nav-actions">
<a href="#terminal" class="btn btn-ghost btn-sm">GUIDE →</a>
<a href="#downloads" class="btn btn-primary btn-sm">DOWNLOAD →</a>
</div>
</div>
</header>
<!-- ── Main Content ── -->
<main>
<!-- ── Hero Section ── -->
<section class="hero-section" id="hero">
<div class="container hero-layout">
<div>
<!-- Terminal prompt -->
<div class="hero-prompt">
<span>~/flowgit/engine</span>
<span class="hero-prompt-dir">--mode=production --target=win64</span>
<span class="hero-prompt-cursor"></span>
</div>
<!-- Main Monospaced Headline -->
<div class="hero-headline">
<span class="tag-label">ENGINEERING-NATIVE GIT CLIENT</span>
<h1 class="heading-display">
TOPOLOGICAL GIT GRAPH.<br>
BLUEPRINT PRECISION.
</h1>
</div>
<!-- Subtitle -->
<p class="hero-description">
FlowGit replaces archaic commit ladders with an interactive, node-based DAG visual engine.
Spawns authentic Git binaries through safe argument arrays with zero shell string interpolation,
yielding instantaneous diffs, porcelain-v1 staging, and SQLite WAL caching for massive enterprise repositories.
</p>
<!-- Action buttons -->
<div class="hero-cta-group">
<a href="https://kazaamo.space/djjdjdbf9/flowgit/-/releases/v1.0.0" target="_blank" rel="noopener noreferrer" class="btn btn-primary btn-lg">
<span>Download on GitLab (Recommended)</span>
<span>→</span>
</a>
<a href="https://github.com/djjdjdbf9/flowgit/releases/latest" target="_blank" rel="noopener noreferrer" class="btn btn-ghost btn-lg">
<span>GitHub Mirror</span>
<span>↗</span>
</a>
<a href="#downloads" class="hero-install-pill" style="cursor: pointer; text-decoration: none;">
<span style="color:var(--color-ember); font-weight:700;">ARTIFACTS:</span>
<code style="color:var(--color-paper);">Portable .exe &bull; Compiled Binaries .zip ↓</code>
</a>
</div>
<!-- Telemetry Stats -->
<div class="hero-telemetry-strip">
<div class="telemetry-item">
<span class="dot"></span>
<span>ZERO SHELL STRINGS</span>
</div>
<div class="telemetry-item">
<span class="dot"></span>
<span>DIRECT ARGV SPAWN</span>
</div>
<div class="telemetry-item">
<span class="dot"></span>
<span>ELECTRON 33 + REACT 18</span>
</div>
<div class="telemetry-item">
<span class="dot"></span>
<span>SQLITE WAL CACHING</span>
</div>
<div class="telemetry-item">
<span class="dot"></span>
<span>AXIOM DESIGN SYSTEM</span>
</div>
</div>
</div>
<!-- Arrow Decoration Field Background (DESIGN.md) -->
<div class="arrow-field-background" aria-hidden="true">&gt; &gt; &gt; &gt; &gt; &gt; &gt; &gt; &gt; &gt; &gt; &gt; &gt; &gt; &gt; &gt; &gt; &gt;
&gt; &gt; &gt; &gt; &gt; &gt; &gt; &gt; &gt; &gt; &gt; &gt; &gt; &gt; &gt; &gt; &gt; &gt;
&gt; &gt; &gt; &gt; &gt; &gt; &gt; &gt; &gt; &gt; &gt; &gt; &gt; &gt; &gt; &gt; &gt; &gt;
&gt; &gt; &gt; &gt; &gt; &gt; &gt; &gt; &gt; &gt; &gt; &gt; &gt; &gt; &gt; &gt; &gt; &gt;
&gt; &gt; &gt; &gt; &gt; &gt; &gt; &gt; &gt; &gt; &gt; &gt; &gt; &gt; &gt; &gt; &gt; &gt;
&gt; &gt; &gt; &gt; &gt; &gt; &gt; &gt; &gt; &gt; &gt; &gt; &gt; &gt; &gt; &gt; &gt; &gt;
&gt; &gt; &gt; &gt; &gt; &gt; &gt; &gt; &gt; &gt; &gt; &gt; &gt; &gt; &gt; &gt; &gt; &gt;
&gt; &gt; &gt; &gt; &gt; &gt; &gt; &gt; &gt; &gt; &gt; &gt; &gt; &gt; &gt; &gt; &gt; &gt;
&gt; &gt; &gt; &gt; &gt; &gt; &gt; &gt; &gt; &gt; &gt; &gt; &gt; &gt; &gt; &gt; &gt; &gt;
&gt; &gt; &gt; &gt; &gt; &gt; &gt; &gt; &gt; &gt; &gt; &gt; &gt; &gt; &gt; &gt; &gt; &gt;
&gt; &gt; &gt; &gt; &gt; &gt; &gt; &gt; &gt; &gt; &gt; &gt; &gt; &gt; &gt; &gt; &gt; &gt;
&gt; &gt; &gt; &gt; &gt; &gt; &gt; &gt; &gt; &gt; &gt; &gt; &gt; &gt; &gt; &gt; &gt; &gt;</div>
</div>
<!-- ── Interactive Product Showcase ── -->
<div class="container" style="position: relative; z-index: 5;">
<div class="showcase-wrapper" id="graph">
<!-- Window Chrome -->
<div class="app-chrome">
<div class="app-window-controls">
<span class="window-dot close"></span>
<span class="window-dot min"></span>
<span class="window-dot max"></span>
</div>
<div class="app-window-title">
<span style="color:var(--color-ember); font-weight:700;">▲</span>
<span>FlowGit — repo: flowgit.git [main*]</span>
</div>
<div class="app-window-status">
<span class="status-indicator-green"></span>
<span>GIT 2.44.0 (x64) • WAL OPEN</span>
</div>
</div>
<!-- Product Tabs -->
<div class="product-tab-bar">
<button class="product-tab active" data-tab="graph">
<span style="color:var(--color-ember);">[01]</span>
<span>BLUEPRINT DAG GRAPH</span>
</button>
<button class="product-tab" data-tab="staging" id="staging">
<span style="color:var(--color-steel);">[02]</span>
<span>PORCELAIN STAGING</span>
</button>
<button class="product-tab" data-tab="diff" id="diffs">
<span style="color:var(--color-steel);">[03]</span>
<span>ADVANCED DIFF VIEWER</span>
</button>
<button class="product-tab" data-tab="branches">
<span style="color:var(--color-steel);">[04]</span>
<span>BRANCH MATRIX &amp; STASH</span>
</button>
</div>
<!-- Commit Activity Histogram (DESIGN.md specification) -->
<div class="histogram-row">
<span class="histogram-label">EVENT FREQUENCY</span>
<div class="histogram-bars-container" id="histogramBars"></div>
<span style="font-size:10px; color:var(--color-steel);">LAST 30 DAYS</span>
</div>
<!-- Showcase Viewport -->
<div class="showcase-viewport">
<!-- TAB 1: BLUEPRINT DAG GRAPH -->
<div class="view-panel active" id="panel-graph">
<div class="blueprint-graph-container" id="graphCanvasContainer">
<!-- Toolbar Controls -->
<div class="graph-controls-toolbar">
<input type="text" id="graphSearchInput" class="graph-search-input" placeholder="Search commits..." />
<button class="graph-tool-btn" id="zoomInBtn" title="Zoom In">+</button>
<button class="graph-tool-btn" id="zoomOutBtn" title="Zoom Out">−</button>
<button class="graph-tool-btn" id="zoomFitBtn" title="Fit to View">FIT</button>
<button class="graph-tool-btn" id="jumpHeadBtn" title="Jump to HEAD" style="color:var(--color-ember); font-weight:700;">HEAD</button>
</div>
<div class="graph-hint">
<span>Pan: Drag canvas | Zoom: Wheel / +/- | Click node to inspect</span>
</div>
<!-- SVG DAG Surface -->
<svg id="graphSvg" class="graph-svg-root"></svg>
<!-- Minimap Radar -->
<div class="graph-minimap">
<div style="font-size:9px; color:var(--color-steel); padding:2px 4px; background:#000;">RADAR</div>
<div class="minimap-viewport-box" id="minimapBox"></div>
</div>
<!-- Commit Inspector Drawer -->
<div class="commit-inspector-drawer" id="commitInspector">
<div class="inspector-header">
<div>
<span style="color:var(--color-ember); font-size:11px; font-weight:700;">COMMIT DETAILS</span>
<div class="inspector-hash" id="inspHash">a94f1c0</div>
</div>
<button class="inspector-close-btn" id="closeInspectorBtn" title="Close drawer">×</button>
</div>
<div class="inspector-body">
<div class="inspector-field">
<div class="inspector-field-label">AUTHOR</div>
<div class="inspector-field-val" id="inspAuthor">Alex Vance &lt;alex@flowgit.io&gt;</div>
</div>
<div class="inspector-field">
<div class="inspector-field-label">DATE &amp; TIME</div>
<div class="inspector-field-val" id="inspDate">2026-10-25 22:41:18</div>
</div>
<div class="inspector-field">
<div class="inspector-field-label">MESSAGE</div>
<div class="inspector-field-val" id="inspMessage" style="color:var(--color-paper); font-weight:500;">
feat(graph): topological DAG bezier connector &amp; minimap sync
</div>
</div>
<div class="inspector-field" style="margin-top:14px;">
<div class="inspector-field-label">CHANGED FILES</div>
<div id="inspFileList" style="border-top:1px solid var(--color-iron); padding-top:6px;"></div>
</div>
<div class="inspector-diff-preview">
<div style="color:var(--color-steel); font-size:10px; margin-bottom:4px;">DIFF HUNK PREVIEW:</div>
<pre id="inspDiffCode" style="color:var(--color-fog); line-height:1.4;"></pre>
</div>
</div>
</div>
</div>
</div>
<!-- TAB 2: PORCELAIN STAGING -->
<div class="view-panel" id="panel-staging">
<div class="staging-view-grid">
<div class="staging-sidebar">
<!-- Staged Files -->
<div class="staging-section-header">
<span>STAGED CHANGES <span id="stagedCount" style="color:var(--color-ember);">(2)</span></span>
<button class="staging-action-btn" id="unstageAllBtn">Unstage All</button>
</div>
<div class="staging-file-list" id="stagedFilesContainer" style="max-height: 140px;"></div>
<!-- Unstaged Files -->
<div class="staging-section-header">
<span>UNSTAGED / UNTRACKED <span id="unstagedCount" style="color:var(--color-steel);">(3)</span></span>
<button class="staging-action-btn" id="stageAllBtn">Stage All</button>
</div>
<div class="staging-file-list" id="unstagedFilesContainer"></div>
<!-- Commit Box -->
<div class="commit-form-panel">
<textarea id="commitInputMsg" class="commit-textarea" placeholder="Commit message (Ctrl+Enter to commit)...">feat(security): sanitize Git environment variables before spawn</textarea>
<div class="commit-form-footer">
<span class="char-counter" id="commitCharCounter">63 / 72 chars</span>
<button class="btn btn-primary btn-sm" id="commitBtn">Commit →</button>
</div>
</div>
</div>
<!-- Staging Diff Pane -->
<div class="staging-diff-preview-pane">
<div style="font-size:12px; color:var(--color-steel); margin-bottom:12px; display:flex; justify-content:space-between;">
<span>DIFF: electron/git/executor.ts</span>
<span style="color:var(--color-fog);">INDEX VS WORKTREE</span>
</div>
<pre style="line-height:1.6; color:var(--color-fog); font-size:12px;"><span style="color:var(--color-ember);">@@ -15,8 +15,10 @@ export function spawnGit(args: string[], cwd: string) {</span>
const gitBinary = getResolvedGitPath();
<span style="color:var(--diff-del-text); background:var(--diff-del-bg); display:block;">- // Dangerous: raw shell invocation with string interpolation</span>
<span style="color:var(--diff-del-text); background:var(--diff-del-bg); display:block;">- return child_process.exec(`"${gitBinary}" ${args.join(' ')}`);</span>
<span style="color:var(--diff-add-text); background:var(--diff-add-bg); display:block;">+ // Safe: argv array spawning with shell disabled</span>
<span style="color:var(--diff-add-text); background:var(--diff-add-bg); display:block;">+ const env = redactSensitiveTokens(process.env);</span>
<span style="color:var(--diff-add-text); background:var(--diff-add-bg); display:block;">+ return child_process.spawn(gitBinary, args, { cwd, shell: false, env });</span>
}</pre>
</div>
</div>
</div>
<!-- TAB 3: ADVANCED DIFF VIEWER -->
<div class="view-panel" id="panel-diff">
<div class="diff-viewer-shell">
<div class="diff-toolbar">
<div class="diff-file-title">
<span style="color:var(--color-ember);">M</span>
<span>electron/git/executor.ts</span>
<span style="font-size:11px; color:var(--color-steel); font-weight:400;">(+7 -1 lines)</span>
</div>
<div class="diff-toggle-group">
<button class="diff-toggle-btn active" id="diffUnifiedBtn">Unified</button>
<button class="diff-toggle-btn" id="diffSplitBtn">Side-by-Side</button>
</div>
</div>
<div class="diff-content-body" id="diffLinesContainer"></div>
</div>
</div>
<!-- TAB 4: BRANCH MATRIX -->
<div class="view-panel" id="panel-branches">
<div class="branches-matrix-shell">
<!-- Local Branches -->
<div class="branch-group-box">
<div class="branch-group-title">
<span>LOCAL BRANCHES</span>
<span style="color:var(--color-steel); font-size:11px;">(3 active)</span>
</div>
<div class="branch-item current">
<div class="branch-name">
<span style="color:var(--color-ember);">*</span>
<span>main</span>
<span style="font-size:10px; padding:2px 6px; background:#202020; border-radius:2px; color:var(--color-fog);">HEAD</span>
</div>
<div class="branch-stats">
<span class="sync-badge">↑1 ahead</span>
<span style="color:var(--color-steel);">• a94f1c0</span>
</div>
</div>
<div class="branch-item">
<div class="branch-name">
<span style="color:var(--color-steel);">o</span>
<span>feature/blueprint-minimap</span>
</div>
<div class="branch-stats">
<span class="sync-badge">↑3 ahead</span>
<span style="color:var(--color-steel);">• f419c82</span>
</div>
</div>
<div class="branch-item">
<div class="branch-name">
<span style="color:var(--color-steel);">o</span>
<span>hotfix/sqlite-wal</span>
</div>
<div class="branch-stats">
<span style="color:var(--color-fog);">synced</span>
<span style="color:var(--color-steel);">• 7b3e9a1</span>
</div>
</div>
</div>
<!-- Stash & Remotes -->
<div class="branch-group-box">
<div class="branch-group-title">
<span>STASH LIST &amp; REMOTES</span>
<span style="color:var(--color-steel); font-size:11px;">(1 stash • 1 remote)</span>
</div>
<div class="branch-item" style="border-left: 2px solid var(--color-slate);">
<div>
<div style="font-weight:700; color:var(--color-paper);">stash@{0}</div>
<div style="font-size:11px; color:var(--color-fog); margin-top:2px;">WIP on main: a94f1c0 feat: minimap radar</div>
</div>
<div style="display:flex; gap:6px;">
<button class="staging-action-btn">Pop</button>
<button class="staging-action-btn">Drop</button>
</div>
</div>
<div style="margin-top:16px; padding-top:12px; border-top:1px solid var(--color-iron);">
<div style="font-size:11px; color:var(--color-steel); margin-bottom:8px;">UPSTREAM REMOTES</div>
<div style="font-size:12px; color:var(--color-fog); background:#111; padding:8px 10px; border-radius:2px; border:1px solid #202020;">
<div style="font-weight:700; color:var(--color-paper);">origin (fetch &amp; push)</div>
<div style="color:var(--color-steel); font-size:11px; margin-top:2px;">https://github.com/flowgit/flowgit.git</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</section>
<!-- ── Features Deep Dive (6 Axiom Cards) ── -->
<section class="section-wrapper" id="features">
<div class="container">
<div class="section-header">
<span class="tag-label">TECHNICAL SPECIFICATIONS</span>
<h2 class="heading-lg">ENGINEERED FOR WORKFLOW VELOCITY</h2>
<p class="body-text" style="max-width: 600px; margin-top: 8px;">
Every component in FlowGit is designed around deterministic Git execution, strict context isolation,
and terminal-grade monospaced ergonomics.
</p>
</div>
<div class="feature-grid">
<!-- Card 1: Editorial Signature Card (with 2px Ember left border) -->
<div class="axiom-card editorial">
<div class="card-num">// 01 &mdash; VISUAL ARCHITECTURE</div>
<h3 class="card-title">Signature Blueprint Git Graph</h3>
<p class="card-body">
Infinite canvas pan-and-zoom DAG renderer. Computes real topological ancestry, non-overlapping lane columns,
and parent-child cubic Bezier splines. Never get lost in sprawling branch merges again.
</p>
<ul class="card-list">
<li>Infinite dot-grid canvas (0.25x to 3.0x zoom)</li>
<li>Live minimap radar with viewport box tracking</li>
<li>Instant jump-to-HEAD and filter search</li>
</ul>
</div>
<!-- Card 2 -->
<div class="axiom-card">
<div class="card-num">// 02 &mdash; PROCESS SECURITY</div>
<h3 class="card-title">Zero Shell Concatenation</h3>
<p class="card-body">
Direct binary invocation via safe argv arrays (<code style="color:var(--color-ember);">spawn('git', [args])</code>).
Eliminates shell escape vulnerabilities, handles spaces and unicode filenames securely, and scrubs credentials before logging.
</p>
<ul class="card-list">
<li>Context isolation with strict IPC channel guards</li>
<li>Automatic token &amp; HTTP basic auth redaction</li>
<li>Multi-stage Windows Git binary detector</li>
</ul>
</div>
<!-- Card 3 -->
<div class="axiom-card">
<div class="card-num">// 03 &mdash; HIGH PERFORMANCE CACHING</div>
<h3 class="card-title">SQLite WAL Persistence</h3>
<p class="card-body">
Instantaneous startup and sub-second repo switching. FlowGit persists repository records, pinned workspaces,
and graph coordinates in an embedded SQLite engine with Write-Ahead Logging (WAL).
</p>
<ul class="card-list">
<li>Zero cold-start lag on 100k+ commit repositories</li>
<li>Atomic disk commits with zero file corruption</li>
<li>Zero cloud dependency &mdash; 100% local data</li>
</ul>
</div>
<!-- Card 4 -->
<div class="axiom-card">
<div class="card-num">// 04 &mdash; STAGING &amp; COMMITS</div>
<h3 class="card-title">Porcelain-v1 Index Staging</h3>
<p class="card-body">
Deterministic file staging based directly on Git's porcelain status format.
Atomic file actions with confirmation safeguards for destructive operations.
</p>
<ul class="card-list">
<li>Clear Staged, Unstaged, Untracked &amp; Conflicted separation</li>
<li>Amend commit flag &amp; live 72-character counter</li>
<li>Selective hunk staging and safe worktree discard</li>
</ul>
</div>
<!-- Card 5 -->
<div class="axiom-card">
<div class="card-num">// 05 &mdash; COMPARISON ENGINE</div>
<h3 class="card-title">Unified &amp; Split Diff Viewer</h3>
<p class="card-body">
Terminal-grade diff parsing engine with additions/deletions highlighting, line numbering for both revisions,
and intelligent context folding for unchanged code.
</p>
<ul class="card-list">
<li>Side-by-side split &amp; unified inline comparison modes</li>
<li>Protective threshold for binary and large files</li>
<li>Design-system safe muted color accents</li>
</ul>
</div>
<!-- Card 6 -->
<div class="axiom-card">
<div class="card-num">// 06 &mdash; BRANCHES &amp; SYNC</div>
<h3 class="card-title">Branch Matrix &amp; Stash Sync</h3>
<p class="card-body">
Complete local and remote-tracking branch management with upstream sync indicators (<code style="color:var(--color-ember);">↑ ahead / ↓ behind</code>),
annotated tags, and untracked-file stash operations.
</p>
<ul class="card-list">
<li>Safe push with force-with-lease protection</li>
<li>Network error classification with actionable recovery</li>
<li>Instant stash preview, pop, apply, and drop</li>
</ul>
</div>
</div>
</div>
</section>
<!-- ── Architecture & Command Guide ── -->
<section class="section-wrapper" id="architecture">
<div class="container">
<div class="section-header">
<span class="tag-label">SYSTEM ARCHITECTURE &amp; EXECUTION</span>
<h2 class="heading-lg">HOW FLOWGIT OPERATES</h2>
<p class="body-text" style="max-width: 600px; margin-top: 8px;">
Inspect the underlying technology choices and execute build commands directly.
</p>
</div>
<div class="arch-grid">
<!-- Terminal Box -->
<div class="terminal-box" id="terminal">
<div class="terminal-tab-header">
<button class="terminal-tab-btn active" data-cmd="portable">PORTABLE (.EXE)</button>
<button class="terminal-tab-btn" data-cmd="binaries">COMPILED BINARIES (.ZIP)</button>
<button class="terminal-tab-btn" data-cmd="source">BUILD FROM SOURCE</button>
</div>
<div class="terminal-code-body">
<button class="copy-floating-btn" id="copyTerminalFloatingBtn">COPY</button>
<pre><code id="terminalCodeDisplay"># Standalone Single-File Executable
# Download FlowGit-Portable.exe from GitLab (Recommended) or GitHub mirror:
.\FlowGit-Portable.exe
# Zero installation, zero registry writes</code></pre>
</div>
</div>
<!-- Architecture Comparison Table -->
<div class="data-table-container">
<table class="axiom-table">
<thead>
<tr>
<th>COMPONENT</th>
<th>FLOWGIT ENGINE</th>
<th>STANDARD GUI CLIENTS</th>
</tr>
</thead>
<tbody>
<tr>
<td style="color:var(--color-paper); font-weight:700;">Git Process</td>
<td style="color:var(--color-ember);">argv spawn() arrays (zero shell)</td>
<td>exec() shell string interpolation</td>
</tr>
<tr>
<td style="color:var(--color-paper); font-weight:700;">Visual Model</td>
<td>Blueprint Topological DAG (Node-based)</td>
<td>Linear vertical ladder list</td>
</tr>
<tr>
<td style="color:var(--color-paper); font-weight:700;">Metadata Store</td>
<td>SQLite WAL database (0ms cold cache)</td>
<td>Volatile in-memory parse on each open</td>
</tr>
<tr>
<td style="color:var(--color-paper); font-weight:700;">Security Model</td>
<td>Context Isolation + Credential Redaction</td>
<td>Plaintext logging of auth URLs</td>
</tr>
<tr>
<td style="color:var(--color-paper); font-weight:700;">Design System</td>
<td>Axiom Dark System (Strict Monospace)</td>
<td>Generic OS native / colorful tabs</td>
</tr>
<tr>
<td style="color:var(--color-paper); font-weight:700;">Runtime</td>
<td>Electron 33 + Vite 6 + React 18</td>
<td>Legacy framework runtimes</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</section>
<!-- ── Download Matrix ── -->
<section class="section-wrapper" id="downloads" style="border-bottom: none;">
<div class="container">
<div class="section-header">
<span class="tag-label">DEPLOYMENT ARTIFACTS</span>
<h2 class="heading-lg">DOWNLOAD FLOWGIT FOR WINDOWS</h2>
<p class="body-text" style="max-width: 680px; margin-top: 8px;">
Targeted for Windows 11 and Windows 10 (64-bit). Primary distribution is hosted on GitLab (<code style="color:var(--color-ember);">Recommended</code>), with an official mirror on GitHub Releases as a backup source.
</p>
</div>
<div class="download-grid">
<!-- Option 1: Portable Single-File -->
<div class="download-card highlight">
<span class="download-badge-recom">RECOMMENDED</span>
<div>
<h3 class="download-card-title">Standalone Portable</h3>
<div class="download-card-meta">FlowGit-Portable.exe &bull; ~82 MB</div>
<ul class="download-card-details">
<li><span>Target OS</span> <span>Windows 11 / 10 (x64)</span></li>
<li><span>Form Factor</span> <span>Single standalone .exe</span></li>
<li><span>Installation</span> <span>Zero registry writes</span></li>
<li><span>Persistence</span> <span>%APPDATA%\FlowGit\</span></li>
</ul>
</div>
<div style="display: flex; flex-direction: column; gap: 8px; margin-top: auto;">
<a href="https://kazaamo.space/djjdjdbf9/flowgit/-/releases/v1.0.0" target="_blank" rel="noopener noreferrer" class="btn btn-primary" style="width: 100%;">
<span>Download (GitLab &bull; Recommended)</span>
<span>→</span>
</a>
<a href="https://github.com/djjdjdbf9/flowgit/releases/latest" target="_blank" rel="noopener noreferrer" class="btn btn-secondary btn-sm" style="width: 100%;">
<span>Backup Mirror (GitHub Releases)</span>
<span>↗</span>
</a>
</div>
</div>
<!-- Option 2: Compiled Binaries Archive -->
<div class="download-card">
<span class="download-badge-recom" style="background-color: var(--color-slate);">ARCHIVE</span>
<div>
<h3 class="download-card-title">Compiled Binaries</h3>
<div class="download-card-meta">FlowGit-win-x64-binaries.zip &bull; ~95 MB</div>
<ul class="download-card-details">
<li><span>Target OS</span> <span>Windows 11 / 10 (x64)</span></li>
<li><span>Contents</span> <span>FlowGit.exe + runtime bundle</span></li>
<li><span>Execution</span> <span>Extract &amp; run FlowGit.exe</span></li>
<li><span>Portability</span> <span>Run from any folder or USB</span></li>
</ul>
</div>
<div style="display: flex; flex-direction: column; gap: 8px; margin-top: auto;">
<a href="https://kazaamo.space/djjdjdbf9/flowgit/-/releases/v1.0.0" target="_blank" rel="noopener noreferrer" class="btn btn-primary" style="width: 100%;">
<span>Download (GitLab &bull; Recommended)</span>
<span>→</span>
</a>
<a href="https://github.com/djjdjdbf9/flowgit/releases/latest" target="_blank" rel="noopener noreferrer" class="btn btn-secondary btn-sm" style="width: 100%;">
<span>Backup Mirror (GitHub Releases)</span>
<span>↗</span>
</a>
</div>
</div>
<!-- Option 3: Source -->
<div class="download-card">
<div>
<h3 class="download-card-title">Source Repository</h3>
<div class="download-card-meta">TypeScript / React 18 / Electron 33</div>
<ul class="download-card-details">
<li><span>Node.js</span> <span>v20.x or v24.x LTS</span></li>
<li><span>Tooling</span> <span>electron-vite + Vitest</span></li>
<li><span>License</span> <span>MIT Open Source</span></li>
<li><span>Verification</span> <span>100% Tests Passing</span></li>
</ul>
</div>
<div style="display: flex; flex-direction: column; gap: 8px; margin-top: auto;">
<a href="https://kazaamo.space/djjdjdbf9/flowgit" target="_blank" rel="noopener noreferrer" class="btn btn-ghost" style="width: 100%;">
<span>GitLab Repository</span>
<span>→</span>
</a>
<a href="https://github.com/djjdjdbf9/flowgit" target="_blank" rel="noopener noreferrer" class="btn btn-secondary btn-sm" style="width: 100%;">
<span>GitHub Mirror</span>
<span>↗</span>
</a>
</div>
</div>
</div>
<!-- System Requirements Strip -->
<div class="sys-reqs-strip">
<div><strong style="color:var(--color-paper);">MINIMUM PREREQUISITES:</strong> Windows 10/11 (64-bit) &bull; 4 GB RAM &bull; Git for Windows 2.30+ installed in PATH</div>
<div style="color:var(--color-steel);">HOSTING: <a href="https://kazaamo.space/djjdjdbf9/flowgit/-/releases/v1.0.0" target="_blank" rel="noopener noreferrer" style="color:var(--color-ember); text-decoration:underline; font-weight:700;">GitLab Releases (Recommended)</a> &bull; <a href="https://github.com/djjdjdbf9/flowgit/releases/latest" target="_blank" rel="noopener noreferrer" style="color:var(--color-fog); text-decoration:underline;">GitHub (Backup Mirror)</a></div>
</div>
</div>
</section>
</main>
<!-- ── Global Keyboard Shortcuts Bar ── -->
<div class="kbd-bar">
<div class="container">
<div class="kbd-items-wrapper">
<span style="color:var(--color-paper); font-weight:700;">KEYBOARD SHORTCUTS:</span>
<div class="kbd-item"><kbd>1</kbd> <span>Graph</span></div>
<div class="kbd-item"><kbd>2</kbd> <span>Staging</span></div>
<div class="kbd-item"><kbd>3</kbd> <span>Diffs</span></div>
<div class="kbd-item"><kbd>4</kbd> <span>Branches</span></div>
<div class="kbd-item"><kbd>H</kbd> <span>Jump HEAD</span></div>
<div class="kbd-item"><kbd>F</kbd> <span>Fit Canvas</span></div>
<div class="kbd-item"><kbd>ESC</kbd> <span>Close Inspector</span></div>
</div>
</div>
</div>
<!-- ── Footer ── -->
<footer class="site-footer">
<div class="container">
<div class="footer-top">
<div>
<div class="brand-logo" style="margin-bottom: 12px;">
<span class="brand-mark">▲</span>
<span>FlowGit</span>
</div>
<p style="font-size: 13px; color: var(--color-fog); max-width: 320px; line-height: 1.6;">
A terminal-grade Git client for Windows engineered with Blueprint visual DAG topology,
porcelain-v1 staging, and hardened argument-array binary execution.
</p>
<div style="font-size: 11px; color: var(--color-steel); margin-top: 14px;">
DESIGN SYSTEM: AXIOM SPECIFICATION v1.0
</div>
</div>
<div>
<h4 class="footer-col-title">RELEASES &amp; MIRRORS</h4>
<ul class="footer-link-list">
<li><a href="https://kazaamo.space/djjdjdbf9/flowgit/-/releases/v1.0.0" target="_blank" rel="noopener noreferrer" class="footer-link" style="color:var(--color-ember);">GitLab Release v1.0.0 →</a></li>
<li><a href="https://github.com/djjdjdbf9/flowgit/releases/latest" target="_blank" rel="noopener noreferrer" class="footer-link">GitHub Latest (Mirror) ↗</a></li>
<li><a href="#downloads" class="footer-link">Standalone Portable .exe</a></li>
<li><a href="#downloads" class="footer-link">Compiled Binaries .zip</a></li>
<li><a href="#architecture" class="footer-link">System Architecture</a></li>
</ul>
</div>
<div>
<h4 class="footer-col-title">DOCUMENTATION</h4>
<ul class="footer-link-list">
<li><a href="#terminal" class="footer-link">Quick Start</a></li>
<li><a href="#terminal" class="footer-link">Build Commands</a></li>
<li><a href="#architecture" class="footer-link">Security Model</a></li>
<li><a href="../DESIGN.md" class="footer-link">DESIGN.md</a></li>
<li><a href="../README.md" class="footer-link">README.md</a></li>
</ul>
</div>
<div>
<h4 class="footer-col-title">ENGINEERING</h4>
<ul class="footer-link-list">
<li><a href="#" class="footer-link">Electron 33</a></li>
<li><a href="#" class="footer-link">React 18 + TypeScript</a></li>
<li><a href="#" class="footer-link">SQLite WAL Persistence</a></li>
<li><a href="#" class="footer-link">Vitest Integration</a></li>
<li><a href="#" class="footer-link">MIT License</a></li>
</ul>
</div>
</div>
<div class="footer-bottom">
<div>&copy; 2026 FlowGit Project. All rights reserved. Licensed under the MIT License.</div>
<div style="display:flex; align-items:center; gap:16px;">
<span style="display:inline-flex; align-items:center; gap:6px;">
<span style="width:6px; height:6px; background:#22c55e; border-radius:9999px;"></span>
<span>SYSTEM STATUS: ALL ENGINES OPERATIONAL</span>
</span>
<span>TERMINAL: ~/FLOWGIT_</span>
</div>
</div>
</div>
</footer>
<!-- ── Global Toast Notice ── -->
<div class="toast-notice" id="globalToast">
<span style="color:var(--color-ember);">[OK]</span>
<span id="toastMsg">Action completed successfully</span>
</div>
<script src="app.js"></script>
</body>
</html>

View File

@ -1,21 +0,0 @@
{
"name": "flowgit-landing",
"version": "1.0.0",
"description": "Terminal-grade Axiom landing page for FlowGit desktop application",
"private": true,
"scripts": {
"start": "npx serve . -l 3000",
"serve": "npx serve . -l 3000",
"preview": "npx serve . -l 3000"
},
"keywords": [
"flowgit",
"git",
"axiom",
"landing-page",
"blueprint",
"terminal"
],
"author": "FlowGit Team",
"license": "MIT"
}

File diff suppressed because it is too large Load Diff