13 KiB
Dealspace — Full Feature Build
Architecture Overview
Go + templ (compiled to _templ.go) + HTMX + SQLite. No external CSS framework CDN — uses Tailwind via the existing class approach already in templates. Server runs on port 8080 (mapped to 9300 externally).
Key files:
internal/model/models.go— data typesinternal/db/migrate.go— schema + seedinternal/handler/*.go— HTTP handlersinternal/store/store.go— DB queriesinternal/rbac/rbac.go— permissionstemplates/*.templ— UI templates (must runtempl generateafter editing)cmd/dealroom/main.go— entrypoint
Build & run:
cd /home/johan/dev/dealroom
templ generate && go build -o bin/dealroom ./cmd/dealroom/main.go
systemctl --user restart dealroom 2>/dev/null || (pkill dealroom; sleep 1; ./bin/dealroom &)
Test user: misha@muskepo.com / Dealspace2026! (owner role, org-1 "Apex Capital Partners")
Current routes (from handler.go): Check internal/handler/handler.go for the router.
WHAT'S ALREADY BUILT (do NOT redo these)
- Auth (login, logout, signup with org creation, view toggle seller↔buyer)
- Dashboard with stage stat cards, last accessed per deal, recent activity linked to deal
- New Room modal (functional, creates deal with name/company/stage/dealsize/IOI/LOI/description)
- Deal rooms list page
- Deal detail with folder tree, file list, request list tabs, Atlas notes, file status change
- "All Documents" virtual folder already in folder tree
- Analytics page with deal filter dropdown
- Audit log with deal filter dropdown
- Contacts page with deal filter dropdown
- Admin panel (CRUD for deals, users, orgs, contacts)
FEATURES TO BUILD
Work through these in order. Commit after each major section with git add -A && git commit -m "feat: ...".
1. Organization Type on Signup
Add org_type field to organizations table: TEXT DEFAULT 'company' CHECK (org_type IN ('bank','pe_vc','company')).
Add migration (additive ALTER TABLE or check-and-add pattern). Add org type selector to signup.templ with options: "Investment Bank", "PE / VC Firm", "Company". Store it.
Show org type in admin panel org edit form.
2. Invite System
Goal: Org owners/admins can invite users by email. Invitee clicks a link, sets password, joins the org.
Schema — add to migrate.go (additive):
CREATE TABLE IF NOT EXISTS invites (
token TEXT PRIMARY KEY,
org_id TEXT NOT NULL,
email TEXT NOT NULL,
role TEXT NOT NULL DEFAULT 'member',
invited_by TEXT NOT NULL,
expires_at DATETIME NOT NULL,
used_at DATETIME,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
Routes:
POST /invites/create— owner/admin only; takes email + role; generates 32-byte hex token, 7-day expiry; stores in DB; returns invite link (just show it in a modal/toast — no email sending needed, Misha will copy/paste)GET /invites/accept?token=XXX— show accept page (name + password fields)POST /invites/accept— validate token not expired/used; create profile in same org; mark invite used
UI:
- Add "Team" page at
/team— lists all profiles in org, shows role badges, invite button - Invite button opens modal: email input + role dropdown (admin/member/viewer) → POST → show generated link
- Nav item "Team" in sidebar (between Contacts and Admin)
3. Remove Close Probability
In admin.templ: remove the close_probability form field (around line 263).
In model/models.go: keep the field (backward compat with existing DB) but don't expose it in any UI.
Search all templates for close_probability or CloseProbability and remove any display.
4. New Room Modal Enhancements
Add to the existing new room modal (in dashboard.templ AND dealrooms.templ):
- Industry field: text input (e.g. "Healthcare", "Fintech", "SaaS")
- Exclusivity End date field (already in Deal model as
ExclusivityEnd, just add to modal form)
Add industry TEXT DEFAULT '' to deals table (additive migration).
Add Industry string to Deal model.
Persist both in handleCreateDeal.
For the "New Room" button on the deal rooms page (dealrooms.templ) — add the same modal that's currently on the dashboard. It should be identical functionality.
Folder auto-create from paths: Add an optional textarea in the modal: "Folder Structure (one path per line, e.g. Financial/Q4 Reports)". Parse on submit in handleCreateDeal: split by newline, for each path split by /, create nested folders. If field is empty, skip.
Invite on create: Add a textarea "Initial Team (one email per line, role: member)". After deal creation, create invite tokens for each email and show all generated links in a success modal. Keep it simple.
5. Permission Controls on Request Lists
Add to deals table (additive):
ALTER TABLE deals ADD COLUMN buyer_can_comment INTEGER DEFAULT 1;
ALTER TABLE deals ADD COLUMN seller_can_comment INTEGER DEFAULT 1;
In deal detail request list view: if buyer_can_comment=0 and user is buyer, hide buyer_comment input. If seller_can_comment=0 and user is seller, hide seller_comment input.
In admin deal edit form: add checkboxes "Allow buyer comments" and "Allow seller comments".
Folder/file visibility per buyer group: Add tables:
CREATE TABLE IF NOT EXISTS buyer_groups (
id TEXT PRIMARY KEY,
deal_id TEXT NOT NULL,
name TEXT NOT NULL,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE IF NOT EXISTS folder_access (
folder_id TEXT NOT NULL,
buyer_group TEXT NOT NULL,
PRIMARY KEY (folder_id, buyer_group)
);
In deal detail documents tab (seller/admin view): add "Restrict Access" button per folder that opens a modal to select which buyer groups can see it. If folder_access has rows for a folder, only those groups see it; if empty = visible to all. Filter files shown to buyer based on their buyer_group (from diligence_requests or a new field on profiles buyer_group TEXT DEFAULT '').
Add buyer_group TEXT DEFAULT '' to profiles table (additive migration). Admin can set it per user.
6. Deal Room: Folder Management
In deal detail documents tab, make the "New Folder" button functional:
- POST
/deals/{id}/folders/createwithnameand optionalparent_id - Handler creates folder, redirects back to deal
Add "Rename" and "Delete" (soft delete or actual) per folder in the sidebar — small kebab menu or inline edit on hover.
Add drag-to-reorder: add sort_order INTEGER DEFAULT 0 to folders (additive). Simple up/down buttons per folder to change order (HTMX swap).
7. File Upload & Management
Real file upload:
Add uploads_dir config (default: data/uploads/).
POST /deals/{id}/files/upload — multipart form: folder_id (optional), request_item_id (optional). Save to data/uploads/{deal_id}/{uuid}_{filename}. Insert file record. Create DealActivity. Redirect back.
Add storage_path TEXT DEFAULT '' to files table.
In document list: add "Upload" button that opens a modal with file picker + folder selector + optional "Link to request item" dropdown.
Add "Delete" per file (owner/admin only): removes from DB + disk. POST /deals/{id}/files/{fileID}/delete.
Add uploaded_at display: files.created_at already exists — show it in the file list as "Uploaded Jan 15" next to file size.
File download: GET /deals/{id}/files/{fileID}/download — serve the file from disk with original filename. Log download activity.
8. Doc ↔ Request List Linking
When uploading a file (see #7), the upload modal has an optional "Link to request item" dropdown showing open request items for this deal. On save, append the new file ID to diligence_requests.linked_file_ids (comma-separated).
In request list view: for each request item with linked_file_ids != "", show linked file names as small pills with download links.
In the upload modal: when a buyer uploads (buyer view), they see only their own request items in the dropdown.
9. Buyer-Specific Request Lists
Add to diligence_requests:
ALTER TABLE diligence_requests ADD COLUMN is_buyer_specific INTEGER DEFAULT 0;
ALTER TABLE diligence_requests ADD COLUMN visible_to_buyer_group TEXT DEFAULT '';
In seller view of request list: show all requests. Add "Add Buyer-Specific Request" button that creates a request visible only to a specific buyer_group.
In buyer view: show general requests (is_buyer_specific=0) AND their own specific ones (visible_to_buyer_group = their buyer_group).
In getRequests() in deals.go: apply this filter for buyer profiles.
10. Document Comments
Schema:
CREATE TABLE IF NOT EXISTS file_comments (
id TEXT PRIMARY KEY,
file_id TEXT NOT NULL,
deal_id TEXT NOT NULL,
user_id TEXT NOT NULL,
content TEXT NOT NULL,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
In file list view: add a chat bubble icon per file. If comments exist, show count badge. Clicking opens a slide-over panel (HTMX hx-get="/deals/{id}/files/{fileID}/comments" → returns comment list + add form).
Routes:
GET /deals/{id}/files/{fileID}/comments— returns HTMX partial with comments + formPOST /deals/{id}/files/{fileID}/comments— add comment, return updated partial
11. Search Within Deal
In deal detail header area, add a search input. On input (debounce 300ms with HTMX), hx-get="/deals/{id}/search?q=..." → searches file names + folder names + request descriptions → returns HTMX partial replacing the active tab content with results.
12. Request Lists Page: Organized by Deal then Buyer
In templates/requests.templ and internal/handler/requests.go:
Restructure to show:
- Deal name as section header
- "General Request List" subsection
- Per buyer_group subsections (if buyer-specific items exist)
Add deal filter in header. Add buyer group filter.
13. Analytics: Per-Buyer Stats
Schema additions:
ALTER TABLE deal_activity ADD COLUMN buyer_group TEXT DEFAULT '';
ALTER TABLE deal_activity ADD COLUMN time_spent_seconds INTEGER DEFAULT 0;
When logging view/download activity, capture buyer_group from profile and time_spent if provided.
In analytics template: when a deal is selected, show a "Buyer Activity" section with a table:
- Buyer Group | Files Downloaded | Last Accessed | Total Time | Files Viewed
- Expandable row: individual user breakdown (hx-get to load per-user stats)
Add handler GET /analytics/buyers?deal_id=XXX returning HTMX partial.
14. Contacts: Deal Association
Add junction table:
CREATE TABLE IF NOT EXISTS contact_deals (
contact_id TEXT NOT NULL,
deal_id TEXT NOT NULL,
PRIMARY KEY (contact_id, deal_id)
);
In contacts page: when deal filter is active, filter by contact_deals join.
In contact add/edit form (admin panel): add multi-select for which deals this contact is associated with.
Show deal name tags on each contact row.
15. Audit Logs: Buyer Filter
Add buyer_group filter dropdown to audit log page alongside the existing deal filter. Query: WHERE deal_id = ? AND (buyer_group = ? OR ? = '').
16. Subscription Plan Page (UI Mock)
Add /subscription route and nav item (gear/settings area or sidebar).
Show three plan cards:
- Starter — 3 deal rooms, 5 users, 10GB storage — $299/mo
- Growth — 15 deal rooms, 25 users, 100GB storage — $799/mo
- Enterprise — Unlimited rooms, unlimited users, 1TB storage — Custom
Show current plan (hardcode "Growth" for demo), usage bars (rooms used/limit, users used/limit, storage used/limit — all mock values based on actual DB counts for rooms/users, mock 23GB for storage).
Add "Upgrade" button per plan that shows a "Contact sales" toast. No real billing.
Implementation Notes
- templ generate: Run after EVERY template change. The
_templ.gofiles must be regenerated or the build fails. - Additive migrations: Always use
ALTER TABLE ADD COLUMN IF NOT EXISTSpattern orCREATE TABLE IF NOT EXISTS. Never drop columns. - HTMX: Use
hx-target,hx-swap,hx-post/getfor partial updates. Return bare HTML fragments from HTMX endpoints (no full Layout wrapper). - Activity logging: Use
logActivity(dealID, userID, orgID, actType, resourceType, resourceName, resourceID)pattern already in deals.go. - Error handling: On bad input, redirect back with
?error=messageand show it in the template. - Buyer view toggle: Check
profile.ViewAsBuyerorprofile.Role == "viewer"to apply buyer restrictions. - File storage: Keep it simple — local disk at
data/uploads/. No S3 needed for now. - Service restart: After build, run
systemctl --user restart dealroomor kill+restart the binary.
After Each Section
templ generatego build -o bin/dealroom ./cmd/dealroom/main.go- Fix any compile errors
systemctl --user restart dealroom(or kill/restart)- Quick smoke test: curl http://localhost:8080/ should redirect to login
git add -A && git commit -m "feat: <section name>"
Final Steps
After all sections:
git add -A && git commit -m "feat: complete Misha feature list"git push origin main- Run:
openclaw system event --text "Done: Built all Dealspace features — invite system, file upload, folder mgmt, buyer-specific requests, doc comments, analytics by buyer, contacts/audit by deal, subscription page" --mode now