# 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 types - `internal/db/migrate.go` — schema + seed - `internal/handler/*.go` — HTTP handlers - `internal/store/store.go` — DB queries - `internal/rbac/rbac.go` — permissions - `templates/*.templ` — UI templates (must run `templ generate` after editing) - `cmd/dealroom/main.go` — entrypoint **Build & run:** ```bash 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):** ```sql 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): ```sql 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: ```sql 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/create` with `name` and optional `parent_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: ```sql 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:** ```sql 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 + form - `POST /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:** ```sql 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: ```sql 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.go` files must be regenerated or the build fails. - **Additive migrations:** Always use `ALTER TABLE ADD COLUMN IF NOT EXISTS` pattern or `CREATE TABLE IF NOT EXISTS`. Never drop columns. - **HTMX:** Use `hx-target`, `hx-swap`, `hx-post/get` for 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=message` and show it in the template. - **Buyer view toggle:** Check `profile.ViewAsBuyer` or `profile.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 dealroom` or kill+restart the binary. ## After Each Section 1. `templ generate` 2. `go build -o bin/dealroom ./cmd/dealroom/main.go` 3. Fix any compile errors 4. `systemctl --user restart dealroom` (or kill/restart) 5. Quick smoke test: curl http://localhost:8080/ should redirect to login 6. `git add -A && git commit -m "feat:
"` ## Final Steps After all sections: 1. `git add -A && git commit -m "feat: complete Misha feature list"` 2. `git push origin main` 3. 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`