Compare commits
No commits in common. "main" and "v1.0.0" have entirely different histories.
@ -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
|
||||
21
README.md
21
README.md
@ -5,12 +5,6 @@
|
||||
|
||||
---
|
||||
|
||||
### Repositories & Downloads
|
||||
- **GitLab (Recommended / Primary):** [https://kazaamo.space/djjdjdbf9/flowgit](https://kazaamo.space/djjdjdbf9/flowgit) • [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) • [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
|
||||
|
||||
BIN
src/icon.ico
BIN
src/icon.ico
Binary file not shown.
|
Before Width: | Height: | Size: 5.1 KiB |
@ -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
|
||||
```
|
||||
903
website/app.js
903
website/app.js
@ -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, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>');
|
||||
}
|
||||
})();
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 5.1 KiB |
@ -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 & 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 • 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">> > > > > > > > > > > > > > > > > >
|
||||
> > > > > > > > > > > > > > > > > >
|
||||
> > > > > > > > > > > > > > > > > >
|
||||
> > > > > > > > > > > > > > > > > >
|
||||
> > > > > > > > > > > > > > > > > >
|
||||
> > > > > > > > > > > > > > > > > >
|
||||
> > > > > > > > > > > > > > > > > >
|
||||
> > > > > > > > > > > > > > > > > >
|
||||
> > > > > > > > > > > > > > > > > >
|
||||
> > > > > > > > > > > > > > > > > >
|
||||
> > > > > > > > > > > > > > > > > >
|
||||
> > > > > > > > > > > > > > > > > ></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 & 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 <alex@flowgit.io></div>
|
||||
</div>
|
||||
<div class="inspector-field">
|
||||
<div class="inspector-field-label">DATE & 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 & 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 & 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 & 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 — 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 — 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 & 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 — 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 — 100% local data</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<!-- Card 4 -->
|
||||
<div class="axiom-card">
|
||||
<div class="card-num">// 04 — STAGING & 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 & Conflicted separation</li>
|
||||
<li>Amend commit flag & 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 — COMPARISON ENGINE</div>
|
||||
<h3 class="card-title">Unified & 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 & 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 — BRANCHES & SYNC</div>
|
||||
<h3 class="card-title">Branch Matrix & 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 & 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 • ~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 • 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 • ~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 & 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 • 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) • 4 GB RAM • 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> • <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 & 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>© 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>
|
||||
@ -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"
|
||||
}
|
||||
1748
website/styles.css
1748
website/styles.css
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user