Knotty is a React + TypeScript app for creating and visualizing a personal relationship network. It includes a left control panel, an interactive force-like graph, and an in-graph edit panel for updating people.
npm install
npm run devProduction checks:
npm run lint
npm run buildThe app uses AWS Amplify Auth against Cognito. Copy .env.example to .env.local, then set your actual Cognito values:
VITE_COGNITO_USER_POOL_ID=us-east-1_yourPoolId
VITE_COGNITO_USER_POOL_CLIENT_ID=yourClientId
VITE_COGNITO_DOMAIN=your-domain.auth.us-east-1.amazoncognito.comIn the Cognito app client settings, make sure these callback and sign-out URLs are allowed:
http://localhost:5173/
https://main.d3c520skipl9wg.amplifyapp.com/
The app includes three photo-upload modes (two active + one scaffold), configured via environment variables:
- Local Fallback (Default): If no AWS configuration is found, photos are resized locally in the browser and stored as Base64 data URLs. No backend is required.
- Direct S3 Upload: Uploads photos directly to an S3 bucket using AWS access keys. Best for quick setup during development.
- Serverless Backend (Scaffold / Not Wired Yet): A Lambda + SAM scaffold exists but is not connected to the frontend upload flow yet.
Add these to your .env.local:
VITE_AWS_REGION=us-east-1
VITE_AWS_ACCESS_KEY_ID=AKIA...
VITE_AWS_SECRET_ACCESS_KEY=...
VITE_S3_BUCKET=your-s3-bucket-nameThere is a SAM backend scaffold in backend/, but the current frontend upload flow does not call
VITE_UPLOAD_URL_ENDPOINT yet. Today, uploads use either:
- local Base64 fallback (default), or
- direct S3 upload via
VITE_AWS_*+VITE_S3_BUCKET.
Use the SAM backend only if you plan to wire presigned URL fetching into src/lib/upload.ts.
- Node model:
name,photo,contact,relationship,notes,groups,source - Group tabs + search
- Interactive Graph: Powered by
d3-force, featuring:- Physics-based layout: Uses
forceSimulationwithforceLink,forceManyBody, andforceCollidefor natural node positioning. - Dynamic Views: Automatically switches between a "Group View" (showing relationship clusters) and a "Person View" (showing individuals within a group).
- Drag-and-drop: Interactive node manipulation that integrates with the physics simulation.
- Edit Panel: Quick updates for relationship, groups, notes, contact info, and photo.
- Physics-based layout: Uses
- LinkedIn CSV import: Fallback when LinkedIn API access is restricted.
- ChatBot: AI-powered assistant for managing your network.
src/
App.tsx # App-level state and feature orchestration
App.css # Main layout/theme styles
index.css # Global baseline styles
components/
Sidebar.tsx # Account card, groups, search, CSV import, add form
GraphView.tsx # Graph rendering, layout simulation, drag/zoom/pan, edit popup
NodeDetail.tsx # Selected node information card
lib/
csv.ts # LinkedIn CSV parsing + node conversion
types/
network.ts # Domain types, constants, seed data, utility helpers
Holds app-wide state:
nodesactiveGroupsearchselectedNodeId
Computes derived state:
groupsvisibleNodesselectedNode
Provides callbacks to child components:
onAddNode(draft)importLinkedInCsv(file)updateNode(nodeId, patch)- selection/filter setters
- Maintains local add-node form state
- Normalizes and submits form data as
NodeDraft - Handles CSV file selection and delegates parsing/import via callback
- Maintains graph-local position, zoom, and pan state.
- Uses
d3-forcesimulations to calculate node positions based on links, collision, and many-body forces. - Renders SVG elements for nodes and edges, with support for both group-level and person-level views.
- Handles interactivity: node dragging, background panning, zooming, and selection.
- Uses a popup edit panel for updating node details and relationship type.
- Parses LinkedIn CSV text
- Maps rows to
PersonNode[]with:source: 'linkedin'- default
relationship: 'Professional' - group tagging (
linkedin, optionalsoftware engineer)
- Domain types (
PersonNode,Relationship,NodeDraft) - Shared constants (
RELATIONSHIP_ORDER,RELATIONSHIP_COLORS) - Helpers (
createNodeId,createAvatarUrl,normalizeRelationship) - Seed data (
initialNodes)
- User action happens in
SidebarorGraphView. - Component calls a callback from
App. Appupdates state (nodes, filters, selection).Apprecomputes derived data (groups,visibleNodes,selectedNode).- Updated props flow back into
SidebarandGraphView.
Update src/types/network.ts:
- Add to
Relationshipunion. - Add to
RELATIONSHIP_ORDER. - Add color in
RELATIONSHIP_COLORS.
- Add field to
PersonNodeandNodeDraftinsrc/types/network.ts. - Update form in
src/components/Sidebar.tsx. - Map draft -> node in
src/App.tsx(onAddNode). - Display field in
src/components/NodeDetail.tsx(if needed). - Update CSV mapper in
src/lib/csv.ts(if import should populate it).
Edit src/components/GraphView.tsx:
- Layout force constants (
pull, damping, ring radius, clamp limits) - Edge generation logic
- SVG visuals and interaction behavior
- Export LinkedIn connections CSV from LinkedIn.
- Use Import LinkedIn CSV in the sidebar.
- Imported rows become professional nodes and are tagged with
linkedin.
- Data Persistence: Node data is currently in-memory only (refresh clears runtime changes).
- Photo Persistence: In Direct S3 mode, photos are stored in S3, but node-to-photo links are still in-memory and reset on refresh.
- Future Work: Connect a database (e.g., DynamoDB) for full persistence of nodes and relationships.