# BLUEPRINT: StackApps — The Stackhouse # Version: 1.0.0 # URL: https://stackapps.app/ # Updated: 2026-04-14 # NOTE: UI automation contract All `### UI` steps use `[data-agent-id="…"]` selectors wired into the live UI. Existing `data-testid` attributes are a separate testing contract and are not used here. ## IDENTITY name: StackApps (The Stackhouse) description: Public directory of browser-ready indie apps with listings, search, and community reviews; signed-in builders submit and manage listings from a private hub. category: productivity contact: https://stackapps.app/ ## AUTH provider: firebase method: oauth, email ## ACCESS last-resort: ui ## CAPABILITY: browse-apps description: Open the public app directory, search and filter approved listings without signing in. input: - name: search-query type: string required: false description: Text to filter listings by name, description, or tags. - name: category type: string required: false description: Directory category label to filter by; must match an existing listing category or All. - name: sort type: string required: false description: Sort control value as implemented in the UI — newest (label “Newest First”) or rating (label “Top Rated”). output: - type: confirmation description: Directory grid visible with optional filters applied. auth-required: false scope: read-only ### UI steps: 1. NAVIGATE /dashboard 2. WAIT [data-agent-id="dashboard-heading"] (max: 15s) 3. VERIFY text_contains [data-agent-id="dashboard-heading"] "The Stackhouse" 4. INPUT [data-agent-id="directory-search"] 5. SELECT [data-agent-id="directory-category"] "" 6. SELECT [data-agent-id="directory-sort"] "" 7. VERIFY selector_exists [data-agent-id="directory-grid"] Note: Run steps 4–6 only when the matching input is provided; if an input is omitted, skip that step and leave the control unchanged. ## CAPABILITY: sign-in description: Sign in with Firebase using email and password. New email accounts use the same page — CLICK [data-agent-id="login-toggle-signup"] once before filling the form, then submit (not modeled as a separate capability here). input: - name: email type: string required: true description: Account email address. - name: password type: string required: true description: Account password (minimum length enforced in UI). output: - type: redirect description: After success, the app navigates to /hub. auth-required: false scope: form-submit ### UI steps: 1. NAVIGATE /login 2. WAIT [data-agent-id="login-heading"] (max: 15s) 3. VERIFY text_contains [data-agent-id="login-heading"] "Welcome to StackApps" 4. INPUT [data-agent-id="login-email"] 5. INPUT [data-agent-id="login-password"] 6. CLICK [data-agent-id="login-email-submit"] 7. WAIT 15s 8. VERIFY url == "/hub" ## CAPABILITY: view-dashboard description: Open the public directory page (The Stackhouse) used as the main browse experience. input: [] output: - type: confirmation description: Dashboard page with directory heading and controls is visible. auth-required: false scope: read-only ### UI steps: 1. NAVIGATE /dashboard 2. WAIT [data-agent-id="dashboard-heading"] (max: 15s) 3. VERIFY text_contains [data-agent-id="dashboard-heading"] "The Stackhouse" ## CAPABILITY: view-app-details description: Open a single listing’s detail page with description, category, ratings summary, reviews, and external launch link. input: - name: app-id type: string required: true description: Firestore document id of the app (same id used in /app/:id). output: - type: confirmation description: Detail page shows the app name and description for the given id. auth-required: false scope: read-only ### UI steps: 1. NAVIGATE /app/ 2. WAIT [data-agent-id="app-detail-name"] (max: 15s) 3. VERIFY selector_exists [data-agent-id="app-detail-name"] 4. VERIFY selector_exists [data-agent-id="app-detail-description"] 5. VERIFY selector_exists [data-agent-id="app-detail-launch"] ## CAPABILITY: submit-app-listing description: From the signed-in hub, open the submission form and create or update a listing (name, URL, description, optional thumbnail, optional category and tags). Listings are stored in Firestore and moderated before appearing in the public directory. input: - name: app-name type: string required: true description: Display name of the product. - name: app-url type: string required: true description: Public HTTPS URL of the live app. - name: description type: string required: true description: Short description shown on the listing. - name: category type: string required: false description: Single category label for the listing; use one of productivity, finance, design, marketing, communication, developer-tools, ecommerce, media, legal, health when possible so directory filters stay consistent. - name: tags type: string required: false description: Comma-separated tags as entered in the tags field. - name: thumbnail-file type: file required: false description: Optional image file for the listing thumbnail; when provided, UPLOAD to [data-agent-id="listing-thumbnail-file"] after filling text fields and before submit. output: - type: confirmation description: New or updated row appears under My Projects in the hub, with moderation status as shown in the UI. auth-required: true scope: form-submit ### UI steps: 1. ASSERT-AUTH 2. NAVIGATE /hub 3. WAIT [data-agent-id="hub-heading"] (max: 15s) 4. VERIFY text_contains [data-agent-id="hub-heading"] "My Apps" 5. CLICK [data-agent-id="hub-submit-new-app"] 6. WAIT [data-agent-id="listing-name"] (max: 10s) 7. INPUT [data-agent-id="listing-name"] 8. INPUT [data-agent-id="listing-url"] 9. INPUT [data-agent-id="listing-description"] 10. INPUT [data-agent-id="listing-category"] 11. INPUT [data-agent-id="listing-tags"] 12. CLICK [data-agent-id="listing-submit"] 13. WAIT 15s 14. VERIFY selector_exists [data-agent-id="hub-my-projects"] Note: When is provided, run UPLOAD [data-agent-id="listing-thumbnail-file"] after step 11 and before step 12. # REFERENCE — data-agent-id attribute locations in JSX # Home.tsx — Link “Browse The Stackhouse” (hero) # data-agent-id="home-browse-stackhouse" # Home.tsx — Link “Browse more on the directory” # data-agent-id="home-browse-more" # Dashboard.tsx — h1 title # data-agent-id="dashboard-heading" # Dashboard.tsx — search Input # data-agent-id="directory-search" # Dashboard.tsx — category SelectTrigger # data-agent-id="directory-category" # Dashboard.tsx — sort SelectTrigger # data-agent-id="directory-sort" # Dashboard.tsx — grid wrapper around AppCard list (the grid div inside #directory) # data-agent-id="directory-grid" # AppCard.tsx — outer Link wrapping each card (static id per app: use data-agent-id={`app-card-${app.id}`}) # pattern: data-agent-id="app-card-" # Login.tsx — h1 # data-agent-id="login-heading" # Login.tsx — email Input # data-agent-id="login-email" # Login.tsx — password Input # data-agent-id="login-password" # Login.tsx — email/password submit Button # data-agent-id="login-email-submit" # Login.tsx — toggle sign up / sign in button # data-agent-id="login-toggle-signup" # Hub.tsx — h1 “My Apps” # data-agent-id="hub-heading" # Hub.tsx — “Submit New App” Button # data-agent-id="hub-submit-new-app" # Hub.tsx — container wrapping the “My Projects” list (outer bg-cyber-gray card) # data-agent-id="hub-my-projects" # Hub.tsx — form name Input # data-agent-id="listing-name" # Hub.tsx — form app URL Input # data-agent-id="listing-url" # Hub.tsx — form description Textarea # data-agent-id="listing-description" # Hub.tsx — form category Input # data-agent-id="listing-category" # Hub.tsx — form tags Input # data-agent-id="listing-tags" # Hub.tsx — form submit Button # data-agent-id="listing-submit" # Hub.tsx — form cancel Button # data-agent-id="listing-cancel" # ImageUpload.tsx — hidden file input element # data-agent-id="listing-thumbnail-file" # AppDetails.tsx — h1 app name # data-agent-id="app-detail-name" # AppDetails.tsx — description paragraph # data-agent-id="app-detail-description" # AppDetails.tsx — “Launch App” anchor # data-agent-id="app-detail-launch"