Compare commits

..
4 Commits
Author SHA1 Message Date
platform-mcp 85965e1a00 feat(contact): add global WhatsApp FAB and mobile sticky CTA bar
Deploy to Production / Build & Verify (push) Has been cancelled
Deploy to Production / Pre-Deploy Tests (push) Has been cancelled
Deploy to Production / Deploy to Railway (push) Has been cancelled
Deploy to Production / Deploy to Render (push) Has been cancelled
Deploy to Production / Deploy to VPS (PM2) (push) Has been cancelled
Deploy to Production / Deploy to Fly.io (push) Has been cancelled
Deploy to Production / Post-Deploy Verification (push) Has been cancelled
Deploy to Production / Notify on Failure (push) Has been cancelled
E2E Test Suite / Critical User Journeys (push) Has been cancelled
E2E Test Suite / API Integration Tests (push) Has been cancelled
E2E Test Suite / Form Interaction Tests (push) Has been cancelled
E2E Test Suite / Destructive & Chaos Tests (push) Has been cancelled
E2E Test Suite / Cross-Browser Regression (chromium) (push) Has been cancelled
E2E Test Suite / Cross-Browser Regression (firefox) (push) Has been cancelled
E2E Test Suite / Cross-Browser Regression (webkit) (push) Has been cancelled
E2E Test Suite / Mobile Device Tests (push) Has been cancelled
E2E Test Suite / Security Header Tests (push) Has been cancelled
E2E Test Suite / Test Report Summary (push) Has been cancelled
E2E Test Suite / Smoke Tests (P0) (push) Has been cancelled
Ping Search Engines / Notify Search Engines (push) Failing after 13m5s
- WhatsAppFab: 56px brand-green floating button (wa.me/919561417403), shown
  on every page; safe-area aware; lifts above the mobile CTA bar on <md.
- MobileStickyCtaBar: 3-column sticky bottom bar (Call / WhatsApp / Quote)
  visible only below the md breakpoint, with safe-area padding, 48px+ touch
  targets, and dark-mode-aware styling. Bottom spacer keeps the footer
  clear of the bar on mobile.
- Mounted both components globally via BaseLayout.
- Added tests/sticky-cta.spec.ts covering 360px and 1280px viewports,
  link integrity, no-overflow, hidden-at-md, and FAB/bar non-overlap.
2026-06-17 11:40:23 +05:30
platform-mcp b9ab4608fd pre-agent: Add WhatsApp FAB and Sticky Mobile CTA Bar 2026-06-17 11:33:31 +05:30
platform-mcp 213aa5c128 agent: Replace Tech Stack Strip With Client Logos 2026-06-17 11:33:10 +05:30
platform-mcp fd42eeade9 pre-agent: Replace Tech Stack Strip With Client Logos 2026-06-17 11:23:34 +05:30
65 changed files with 8759 additions and 1120 deletions
+1
View File
@@ -0,0 +1 @@
/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e
+151 -193
View File
@@ -1,253 +1,211 @@
# Graph Report - /home/agent/ai-agent/backend/workspace/73f46153-e8cc-447a-82ad-2eeefa00689f (2026-04-14)
# Graph Report - 2e709500-79ab-403f-9ca0-3f027ac9f36e (2026-06-17)
## Corpus Check
- 43 files · ~211,373 words
- 56 files · ~324,046 words
- Verdict: corpus is large enough that graph structure adds value.
## Summary
- 163 nodes · 187 edges · 42 communities detected
- Extraction: 100% EXTRACTED · 0% INFERRED · 0% AMBIGUOUS
- 518 nodes · 563 edges · 91 communities (84 shown, 7 thin omitted)
- Extraction: 99% EXTRACTED · 1% INFERRED · 0% AMBIGUOUS · INFERRED: 4 edges (avg confidence: 0.8)
- Token cost: 0 input · 0 output
## Graph Freshness
- Built from commit: `41a21312`
- Run `git rev-parse HEAD` and compare to check if the graph is stale.
- Run `graphify update .` after code changes (no API cost).
## Community Hubs (Navigation)
- [[_COMMUNITY_Community 0|Community 0]]
- [[_COMMUNITY_Community 1|Community 1]]
- [[_COMMUNITY_Community 2|Community 2]]
- [[_COMMUNITY_Community 3|Community 3]]
- [[_COMMUNITY_Community 4|Community 4]]
- [[_COMMUNITY_Community 5|Community 5]]
- [[_COMMUNITY_Community 6|Community 6]]
- [[_COMMUNITY_Community 7|Community 7]]
- [[_COMMUNITY_Community 9|Community 9]]
- [[_COMMUNITY_Community 10|Community 10]]
- [[_COMMUNITY_Community 12|Community 12]]
- [[_COMMUNITY_Community 13|Community 13]]
- [[_COMMUNITY_Community 14|Community 14]]
- [[_COMMUNITY_Community 15|Community 15]]
- [[_COMMUNITY_Community 16|Community 16]]
- [[_COMMUNITY_Community 17|Community 17]]
- [[_COMMUNITY_Community 18|Community 18]]
- [[_COMMUNITY_Community 19|Community 19]]
- [[_COMMUNITY_Community 20|Community 20]]
- [[_COMMUNITY_Community 21|Community 21]]
- [[_COMMUNITY_Community 22|Community 22]]
- [[_COMMUNITY_Community 23|Community 23]]
- [[_COMMUNITY_Community 24|Community 24]]
- [[_COMMUNITY_Community 25|Community 25]]
- [[_COMMUNITY_Community 26|Community 26]]
- [[_COMMUNITY_Community 27|Community 27]]
- [[_COMMUNITY_Community 28|Community 28]]
- [[_COMMUNITY_Community 29|Community 29]]
- [[_COMMUNITY_Community 30|Community 30]]
- [[_COMMUNITY_Community 31|Community 31]]
- [[_COMMUNITY_Community 32|Community 32]]
- [[_COMMUNITY_Community 33|Community 33]]
- [[_COMMUNITY_Community 34|Community 34]]
- [[_COMMUNITY_Community 35|Community 35]]
- [[_COMMUNITY_Community 40|Community 40]]
## God Nodes (most connected - your core abstractions)
1. `ContactPage` - 10 edges
2. `NavigationPage` - 8 edges
3. `main()` - 7 edges
4. `trackEvent()` - 6 edges
5. `captureException()` - 6 edges
6. `captureMessage()` - 6 edges
7. `POST()` - 5 edges
8. `POST()` - 5 edges
9. `initAllAnimations()` - 5 edges
10. `log()` - 5 edges
1. `Performance Testing Scripts` - 12 edges
2. `captureException()` - 11 edges
3. `ContactPage` - 10 edges
4. `ContactPage` - 10 edges
5. `Astro SSR Standalone Server` - 9 edges
6. `Backup Scripts` - 9 edges
7. `NavigationPage` - 8 edges
8. `Changed - Domain Migration (2026-03-21)` - 8 edges
9. `NavigationPage` - 8 edges
10. `main()` - 7 edges
## Surprising Connections (you probably didn't know these)
- None detected - all connections are within the same source files.
- `sendContactEmail()` --calls--> `captureException()` [INFERRED]
src/pages/api/contact.ts → src/utils/sentry.ts
- `POST()` --calls--> `logApiRequest()` [INFERRED]
src/pages/api/contact.ts → src/utils/logger.ts
- `subscribeToNewsletter()` --calls--> `captureException()` [INFERRED]
src/pages/api/newsletter.ts → src/utils/sentry.ts
- `POST()` --calls--> `logApiRequest()` [INFERRED]
src/pages/api/newsletter.ts → src/utils/logger.ts
## Communities
## Communities (91 total, 7 thin omitted)
### Community 0 - "Community 0"
Cohesion: 0.17
Nodes (0):
Cohesion: 0.05
Nodes (37): Architecture, Astro SSR Standalone Server, Available Scripts, Build Failures, Build Process, code:bash (npm install), code:dockerfile (FROM node:18-alpine), code:block11 (.) (+29 more)
### Community 1 - "Community 1"
Cohesion: 0.22
Nodes (1): ContactPage
Cohesion: 0.05
Nodes (37): 1. `performance_test.py`, 2. `quick-perf-check.sh`, 3. `lighthouse_audit.py`, Available Scripts, Basic Response Time, Best Practices, CI/CD Integration, code:bash (# Quick check (recommended)) (+29 more)
### Community 2 - "Community 2"
Cohesion: 0.56
Nodes (8): captureException(), captureMessage(), generateEventId(), getEnvironment(), getRelease(), getSentryDsn(), parseDsn(), sendToSentry()
Cohesion: 0.14
Nodes (27): checkRateLimit(), corsHeaders(), escapeHtml(), OPTIONS(), POST(), sendContactEmail(), validateContactForm(), checkRateLimit() (+19 more)
### Community 3 - "Community 3"
Cohesion: 0.36
Nodes (6): formatEntry(), getMinLevel(), log(), logApiRequest(), output(), shouldLog()
Cohesion: 0.16
Nodes (20): formatEntry(), getMinLevel(), log(), logApiRequest(), output(), shouldLog(), checkRateLimit(), corsHeaders() (+12 more)
### Community 4 - "Community 4"
Cohesion: 0.22
Nodes (1): NavigationPage
Cohesion: 0.09
Nodes (22): [1.0.0] - Initial Release, Added, Added, API Routes, Changed, Changed - Domain Migration (2026-03-21), Changelog, Configuration (+14 more)
### Community 5 - "Community 5"
Cohesion: 0.46
Nodes (7): checkRateLimit(), corsHeaders(), escapeHtml(), OPTIONS(), POST(), sendContactEmail(), validateContactForm()
Cohesion: 0.09
Nodes (21): Automation Setup, Available Commands, Backup fails, Backup Scripts, code:bash (# First-time setup), code:bash (# Edit crontab), code:powershell (# Daily backup), code:bash (# List available backups) (+13 more)
### Community 6 - "Community 6"
Cohesion: 0.12
Nodes (15): 1. Modular Monolith Over Premature Microservices, 2. Role-Based Access Control (RBAC) from Day One, 3. Offline-Resilient Design, Architecture Decisions That Scale, Blue-Green Deployments, code:block1 (src/), code:typescript (// Example: Contextual data filtering), code:sql (-- Row-level tenant isolation) (+7 more)
### Community 7 - "Community 7"
Cohesion: 0.14
Nodes (13): 1. Multi-District Deployment Complexity, 2. Security and Compliance, 3. Reliability Under Constraint, Architecture Decisions That Paid Off, code:java (@Configuration), code:java (@Service), code:java (@Component), code:yaml (# District-specific feature configuration) (+5 more)
### Community 9 - "Community 9"
Cohesion: 0.17
Nodes (11): 1. Customer Service Automation, 2. Predictive Analytics, 3. Process Automation, 4. Intelligent Document Processing, code:python (from transformers import pipeline), code:python (import pandas as pd), code:python (# Example: Extracting structured data from forms), Implementation Considerations (+3 more)
### Community 10 - "Community 10"
Cohesion: 0.17
Nodes (11): Code Example, code:astro (---), code:javascript (// Traditional SPA: Everything loads upfront), code:typescript (// src/content/config.ts), code:bash (npm create astro@latest), Content Collections: Type-Safe Content, Getting Started, How Astro Differs from Traditional Frameworks (+3 more)
### Community 13 - "Community 13"
Cohesion: 0.18
Nodes (10): code:hcl (# Define AWS EC2 instance), code:dockerfile (# Multi-stage build for a Go application), code:yaml (# AWS Cost allocation tags), Common Pitfalls, Conclusion, Containerization as a Migration Strategy, Cost Optimization Strategies, Infrastructure as Code (+2 more)
### Community 17 - "Community 17"
Cohesion: 0.43
Nodes (6): trackCaseStudyView(), trackEvent(), trackExternalLink(), trackFormSubmit(), trackNewsletterSignup(), trackPortfolioFilter()
### Community 7 - "Community 7"
### Community 18 - "Community 18"
Cohesion: 0.36
Nodes (7): extract_scores(), get_issues(), main(), Run Lighthouse audit on a URL, Extract key scores from Lighthouse data, Extract issues from Lighthouse audit, run_lighthouse()
### Community 8 - "Community 8"
### Community 19 - "Community 19"
Cohesion: 0.46
Nodes (7): captureScreenshot(), ensureDir(), generateIndex(), main(), screenshotPath(), setTheme(), waitForPageReady()
### Community 9 - "Community 9"
### Community 20 - "Community 20"
Cohesion: 0.36
Nodes (4): deleteFromDB(), getAllFromDB(), openDB(), processSyncQueue()
### Community 10 - "Community 10"
Cohesion: 0.52
Nodes (6): checkRateLimit(), corsHeaders(), OPTIONS(), POST(), subscribeToNewsletter(), validateEmail()
### Community 21 - "Community 21"
Cohesion: 0.46
Nodes (7): checkRateLimit(), corsHeaders(), escapeHtml(), OPTIONS(), POST(), sendContactEmail(), validateContactForm()
### Community 11 - "Community 11"
### Community 22 - "Community 22"
Cohesion: 0.43
Nodes (6): trackCaseStudyView(), trackEvent(), trackExternalLink(), trackFormSubmit(), trackNewsletterSignup(), trackPortfolioFilter()
### Community 23 - "Community 23"
Cohesion: 0.36
Nodes (7): extract_scores(), get_issues(), main(), Run Lighthouse audit on a URL, Extract key scores from Lighthouse data, Extract issues from Lighthouse audit, run_lighthouse()
### Community 24 - "Community 24"
Cohesion: 0.46
Nodes (7): captureScreenshot(), ensureDir(), generateIndex(), main(), screenshotPath(), setTheme(), waitForPageReady()
### Community 25 - "Community 25"
Cohesion: 0.36
Nodes (4): deleteFromDB(), getAllFromDB(), openDB(), processSyncQueue()
### Community 26 - "Community 26"
Cohesion: 0.48
Nodes (5): initAllAnimations(), initButtonRipple(), initCounters(), initProgressBars(), initScrollReveal()
### Community 12 - "Community 12"
### Community 27 - "Community 27"
Cohesion: 0.48
Nodes (5): extractJsonLd(), main(), validateOpenGraph(), validatePage(), validateTwitterCard()
### Community 13 - "Community 13"
### Community 28 - "Community 28"
Cohesion: 0.48
Nodes (5): initAllAnimations(), initButtonRipple(), initCounters(), initProgressBars(), initScrollReveal()
### Community 29 - "Community 29"
Cohesion: 0.48
Nodes (5): extractJsonLd(), main(), validateOpenGraph(), validatePage(), validateTwitterCard()
### Community 30 - "Community 30"
Cohesion: 0.47
Nodes (5): main(), measure_response_time(), print_stats(), Measure response time for a URL over multiple iterations, Print statistics for response times
### Community 14 - "Community 14"
Cohesion: 0.4
Nodes (2): loadWithTheme(), setStoredTheme()
### Community 32 - "Community 32"
Cohesion: 0.47
Nodes (5): main(), measure_response_time(), print_stats(), Measure response time for a URL over multiple iterations, Print statistics for response times
### Community 15 - "Community 15"
### Community 34 - "Community 34"
Cohesion: 0.6
Nodes (3): formatUptime(), GET(), percentile()
### Community 16 - "Community 16"
Cohesion: 0.5
Nodes (0):
### Community 17 - "Community 17"
Cohesion: 0.5
Nodes (0):
### Community 18 - "Community 18"
Cohesion: 1.0
Nodes (0):
### Community 19 - "Community 19"
Cohesion: 1.0
Nodes (0):
### Community 20 - "Community 20"
Cohesion: 1.0
Nodes (0):
### Community 21 - "Community 21"
Cohesion: 1.0
Nodes (0):
### Community 22 - "Community 22"
Cohesion: 1.0
Nodes (0):
### Community 23 - "Community 23"
Cohesion: 1.0
Nodes (0):
### Community 24 - "Community 24"
Cohesion: 1.0
Nodes (0):
### Community 25 - "Community 25"
Cohesion: 1.0
Nodes (0):
### Community 26 - "Community 26"
Cohesion: 1.0
Nodes (0):
### Community 27 - "Community 27"
Cohesion: 1.0
Nodes (0):
### Community 28 - "Community 28"
Cohesion: 1.0
Nodes (0):
### Community 29 - "Community 29"
Cohesion: 1.0
Nodes (0):
### Community 30 - "Community 30"
Cohesion: 1.0
Nodes (0):
### Community 31 - "Community 31"
Cohesion: 1.0
Nodes (0):
### Community 32 - "Community 32"
Cohesion: 1.0
Nodes (0):
### Community 33 - "Community 33"
Cohesion: 1.0
Nodes (0):
### Community 34 - "Community 34"
Cohesion: 1.0
Nodes (0):
### Community 35 - "Community 35"
Cohesion: 1.0
Nodes (0):
### Community 36 - "Community 36"
Cohesion: 1.0
Nodes (0):
### Community 37 - "Community 37"
Cohesion: 1.0
Nodes (0):
### Community 38 - "Community 38"
Cohesion: 1.0
Nodes (0):
### Community 39 - "Community 39"
Cohesion: 1.0
Nodes (0):
### Community 40 - "Community 40"
Cohesion: 1.0
Nodes (0):
### Community 41 - "Community 41"
Cohesion: 1.0
Nodes (0):
Cohesion: 0.6
Nodes (3): formatUptime(), GET(), percentile()
## Knowledge Gaps
- **5 isolated node(s):** `Run Lighthouse audit on a URL`, `Extract key scores from Lighthouse data`, `Extract issues from Lighthouse audit`, `Measure response time for a URL over multiple iterations`, `Print statistics for response times`
- **111 isolated node(s):** `Run Lighthouse audit on a URL`, `Extract key scores from Lighthouse data`, `Extract issues from Lighthouse audit`, `Measure response time for a URL over multiple iterations`, `Print statistics for response times` (+106 more)
These have ≤1 connection - possible missing edges or undocumented components.
- **Thin community `Community 18`** (2 nodes): `sitemap.xml.ts`, `GET()`
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
- **Thin community `Community 19`** (2 nodes): `sitemap-index.xml.ts`, `GET()`
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
- **Thin community `Community 20`** (2 nodes): `health.json.ts`, `GET()`
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
- **Thin community `Community 21`** (1 nodes): `playwright.config.ts`
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
- **Thin community `Community 22`** (1 nodes): `env.d.ts`
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
- **Thin community `Community 23`** (1 nodes): `config.ts`
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
- **Thin community `Community 24`** (1 nodes): `windows-tasks.ps1`
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
- **Thin community `Community 25`** (1 nodes): `e2e-blog-navigation.spec.ts`
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
- **Thin community `Community 26`** (1 nodes): `e2e-critical-paths.spec.ts`
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
- **Thin community `Community 27`** (1 nodes): `navigation.spec.ts`
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
- **Thin community `Community 28`** (1 nodes): `contact-form.spec.ts`
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
- **Thin community `Community 29`** (1 nodes): `e2e-form-interactions.spec.ts`
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
- **Thin community `Community 30`** (1 nodes): `destructive-chaos.spec.ts`
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
- **Thin community `Community 31`** (1 nodes): `security-headers.test.ts`
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
- **Thin community `Community 32`** (1 nodes): `bug-fix-verification.spec.ts`
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
- **Thin community `Community 33`** (1 nodes): `pages.spec.ts`
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
- **Thin community `Community 34`** (1 nodes): `api-integration.spec.ts`
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
- **Thin community `Community 35`** (1 nodes): `static-assets.spec.ts`
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
- **Thin community `Community 36`** (1 nodes): `accessibility.spec.ts`
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
- **Thin community `Community 37`** (1 nodes): `cross-browser.spec.ts`
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
- **Thin community `Community 38`** (1 nodes): `newsletter-subscription.spec.ts`
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
- **Thin community `Community 39`** (1 nodes): `portfolio.spec.ts`
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
- **Thin community `Community 40`** (1 nodes): `blog.spec.ts`
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
- **Thin community `Community 41`** (1 nodes): `e2e-smoke-suite.spec.ts`
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
- **7 thin communities (<3 nodes) omitted from report** — run `graphify query` to explore isolated nodes.
## Suggested Questions
_Questions this graph is uniquely positioned to answer:_
- **Are the 2 inferred relationships involving `captureException()` (e.g. with `sendContactEmail()` and `subscribeToNewsletter()`) actually correct?**
_`captureException()` has 2 INFERRED edges - model-reasoned connections that need verification._
- **What connects `Run Lighthouse audit on a URL`, `Extract key scores from Lighthouse data`, `Extract issues from Lighthouse audit` to the rest of the system?**
_5 weakly-connected nodes found - possible documentation gaps or missing edges._
_111 weakly-connected nodes found - possible documentation gaps or missing edges._
- **Should `Community 0` be split into smaller, more focused modules?**
_Cohesion score 0.05 - nodes in this community are weakly interconnected._
- **Should `Community 1` be split into smaller, more focused modules?**
_Cohesion score 0.05 - nodes in this community are weakly interconnected._
- **Should `Community 2` be split into smaller, more focused modules?**
_Cohesion score 0.14 - nodes in this community are weakly interconnected._
- **Should `Community 4` be split into smaller, more focused modules?**
_Cohesion score 0.09 - nodes in this community are weakly interconnected._
- **Should `Community 5` be split into smaller, more focused modules?**
_Cohesion score 0.09 - nodes in this community are weakly interconnected._
@@ -0,0 +1 @@
{"nodes": [{"id": "home_agent_ai_agent_backend_workspace_2e709500_79ab_403f_9ca0_3f027ac9f36e_tests_navigation_spec_ts", "label": "navigation.spec.ts", "file_type": "code", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/tests/navigation.spec.ts", "source_location": "L1"}], "edges": [{"source": "home_agent_ai_agent_backend_workspace_2e709500_79ab_403f_9ca0_3f027ac9f36e_tests_navigation_spec_ts", "target": "test", "relation": "imports_from", "context": "import", "confidence": "EXTRACTED", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/tests/navigation.spec.ts", "source_location": "L1", "weight": 1.0}], "raw_calls": []}
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
{"nodes": [{"id": "home_agent_ai_agent_backend_workspace_2e709500_79ab_403f_9ca0_3f027ac9f36e_server_mjs", "label": "server.mjs", "file_type": "code", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/server.mjs", "source_location": "L1"}], "edges": [{"source": "home_agent_ai_agent_backend_workspace_2e709500_79ab_403f_9ca0_3f027ac9f36e_server_mjs", "target": "dotenv", "relation": "imports_from", "context": "import", "confidence": "EXTRACTED", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/server.mjs", "source_location": "L1", "weight": 1.0}, {"source": "home_agent_ai_agent_backend_workspace_2e709500_79ab_403f_9ca0_3f027ac9f36e_server_mjs", "target": "url", "relation": "imports_from", "context": "import", "confidence": "EXTRACTED", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/server.mjs", "source_location": "L2", "weight": 1.0}, {"source": "home_agent_ai_agent_backend_workspace_2e709500_79ab_403f_9ca0_3f027ac9f36e_server_mjs", "target": "path", "relation": "imports_from", "context": "import", "confidence": "EXTRACTED", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/server.mjs", "source_location": "L3", "weight": 1.0}, {"source": "home_agent_ai_agent_backend_workspace_2e709500_79ab_403f_9ca0_3f027ac9f36e_server_mjs", "target": "home_agent_ai_agent_backend_workspace_2e709500_79ab_403f_9ca0_3f027ac9f36e_dist_server_entry_mjs", "relation": "imports_from", "context": "import", "confidence": "EXTRACTED", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/server.mjs", "source_location": "L14", "weight": 1.0}, {"source": "home_agent_ai_agent_backend_workspace_2e709500_79ab_403f_9ca0_3f027ac9f36e_server_mjs", "target": "server_entry_handler", "relation": "imports", "context": "import", "confidence": "EXTRACTED", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/server.mjs", "source_location": "L14", "weight": 1.0}, {"source": "home_agent_ai_agent_backend_workspace_2e709500_79ab_403f_9ca0_3f027ac9f36e_server_mjs", "target": "express", "relation": "imports_from", "context": "import", "confidence": "EXTRACTED", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/server.mjs", "source_location": "L15", "weight": 1.0}, {"source": "home_agent_ai_agent_backend_workspace_2e709500_79ab_403f_9ca0_3f027ac9f36e_server_mjs", "target": "compression", "relation": "imports_from", "context": "import", "confidence": "EXTRACTED", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/server.mjs", "source_location": "L16", "weight": 1.0}], "raw_calls": []}
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
{"nodes": [{"id": "home_agent_ai_agent_backend_workspace_2e709500_79ab_403f_9ca0_3f027ac9f36e_test_results_newsletter_subscription_ne_c1de9_isible_and_usable_on_mobile_chromium_error_context_md", "label": "error-context.md", "file_type": "document", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/test-results/newsletter-subscription-Ne-c1de9-isible-and-usable-on-mobile-chromium/error-context.md", "source_location": "L1"}, {"id": "newsletter_subscription_ne_c1de9_isible_and_usable_on_mobile_chromium_error_context_page_snapshot", "label": "Page snapshot", "file_type": "document", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/test-results/newsletter-subscription-Ne-c1de9-isible-and-usable-on-mobile-chromium/error-context.md", "source_location": "L1"}, {"id": "newsletter_subscription_ne_c1de9_isible_and_usable_on_mobile_chromium_error_context_codeblock_1", "label": "code:yaml (- generic [active] [ref=e1]:)", "file_type": "document", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/test-results/newsletter-subscription-Ne-c1de9-isible-and-usable-on-mobile-chromium/error-context.md", "source_location": "L3"}], "edges": [{"source": "home_agent_ai_agent_backend_workspace_2e709500_79ab_403f_9ca0_3f027ac9f36e_test_results_newsletter_subscription_ne_c1de9_isible_and_usable_on_mobile_chromium_error_context_md", "target": "newsletter_subscription_ne_c1de9_isible_and_usable_on_mobile_chromium_error_context_page_snapshot", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/test-results/newsletter-subscription-Ne-c1de9-isible-and-usable-on-mobile-chromium/error-context.md", "source_location": "L1", "weight": 1.0}, {"source": "newsletter_subscription_ne_c1de9_isible_and_usable_on_mobile_chromium_error_context_page_snapshot", "target": "newsletter_subscription_ne_c1de9_isible_and_usable_on_mobile_chromium_error_context_codeblock_1", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/test-results/newsletter-subscription-Ne-c1de9-isible-and-usable-on-mobile-chromium/error-context.md", "source_location": "L3", "weight": 1.0}], "input_tokens": 0, "output_tokens": 0}
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
{"nodes": [{"id": "home_agent_ai_agent_backend_workspace_2e709500_79ab_403f_9ca0_3f027ac9f36e_tests_e2e_form_interactions_spec_ts", "label": "e2e-form-interactions.spec.ts", "file_type": "code", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/tests/e2e-form-interactions.spec.ts", "source_location": "L1"}], "edges": [{"source": "home_agent_ai_agent_backend_workspace_2e709500_79ab_403f_9ca0_3f027ac9f36e_tests_e2e_form_interactions_spec_ts", "target": "test", "relation": "imports_from", "context": "import", "confidence": "EXTRACTED", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/tests/e2e-form-interactions.spec.ts", "source_location": "L1", "weight": 1.0}], "raw_calls": []}
@@ -0,0 +1 @@
{"nodes": [{"id": "home_agent_ai_agent_backend_workspace_2e709500_79ab_403f_9ca0_3f027ac9f36e_tests_api_integration_spec_ts", "label": "api-integration.spec.ts", "file_type": "code", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/tests/api-integration.spec.ts", "source_location": "L1"}], "edges": [{"source": "home_agent_ai_agent_backend_workspace_2e709500_79ab_403f_9ca0_3f027ac9f36e_tests_api_integration_spec_ts", "target": "test", "relation": "imports_from", "context": "import", "confidence": "EXTRACTED", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/tests/api-integration.spec.ts", "source_location": "L1", "weight": 1.0}], "raw_calls": []}
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
{"nodes": [{"id": "home_agent_ai_agent_backend_workspace_2e709500_79ab_403f_9ca0_3f027ac9f36e_tests_bug_fix_verification_spec_ts", "label": "bug-fix-verification.spec.ts", "file_type": "code", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/tests/bug-fix-verification.spec.ts", "source_location": "L1"}], "edges": [{"source": "home_agent_ai_agent_backend_workspace_2e709500_79ab_403f_9ca0_3f027ac9f36e_tests_bug_fix_verification_spec_ts", "target": "test", "relation": "imports_from", "context": "import", "confidence": "EXTRACTED", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/tests/bug-fix-verification.spec.ts", "source_location": "L13", "weight": 1.0}], "raw_calls": []}
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
{"nodes": [{"id": "home_agent_ai_agent_backend_workspace_2e709500_79ab_403f_9ca0_3f027ac9f36e_src_pages_sitemap_xml_ts", "label": "sitemap.xml.ts", "file_type": "code", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/src/pages/sitemap.xml.ts", "source_location": "L1"}, {"id": "pages_sitemap_xml_get", "label": "GET()", "file_type": "code", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/src/pages/sitemap.xml.ts", "source_location": "L13"}], "edges": [{"source": "home_agent_ai_agent_backend_workspace_2e709500_79ab_403f_9ca0_3f027ac9f36e_src_pages_sitemap_xml_ts", "target": "astro", "relation": "imports_from", "context": "import", "confidence": "EXTRACTED", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/src/pages/sitemap.xml.ts", "source_location": "L1", "weight": 1.0}, {"source": "home_agent_ai_agent_backend_workspace_2e709500_79ab_403f_9ca0_3f027ac9f36e_src_pages_sitemap_xml_ts", "target": "astro_content", "relation": "imports_from", "context": "import", "confidence": "EXTRACTED", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/src/pages/sitemap.xml.ts", "source_location": "L2", "weight": 1.0}, {"source": "home_agent_ai_agent_backend_workspace_2e709500_79ab_403f_9ca0_3f027ac9f36e_src_pages_sitemap_xml_ts", "target": "pages_sitemap_xml_get", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/src/pages/sitemap.xml.ts", "source_location": "L13", "weight": 1.0}], "raw_calls": [{"caller_nid": "pages_sitemap_xml_get", "callee": "getCollection", "is_member_call": false, "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/src/pages/sitemap.xml.ts", "source_location": "L15"}, {"caller_nid": "pages_sitemap_xml_get", "callee": "getCollection", "is_member_call": false, "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/src/pages/sitemap.xml.ts", "source_location": "L20"}, {"caller_nid": "pages_sitemap_xml_get", "callee": "map", "is_member_call": true, "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/src/pages/sitemap.xml.ts", "source_location": "L37"}, {"caller_nid": "pages_sitemap_xml_get", "callee": "map", "is_member_call": true, "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/src/pages/sitemap.xml.ts", "source_location": "L46"}, {"caller_nid": "pages_sitemap_xml_get", "callee": "join", "is_member_call": true, "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/src/pages/sitemap.xml.ts", "source_location": "L63"}, {"caller_nid": "pages_sitemap_xml_get", "callee": "map", "is_member_call": true, "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/src/pages/sitemap.xml.ts", "source_location": "L63"}]}
@@ -0,0 +1 @@
{"nodes": [{"id": "home_agent_ai_agent_backend_workspace_2e709500_79ab_403f_9ca0_3f027ac9f36e_tests_static_assets_spec_ts", "label": "static-assets.spec.ts", "file_type": "code", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/tests/static-assets.spec.ts", "source_location": "L1"}], "edges": [{"source": "home_agent_ai_agent_backend_workspace_2e709500_79ab_403f_9ca0_3f027ac9f36e_tests_static_assets_spec_ts", "target": "test", "relation": "imports_from", "context": "import", "confidence": "EXTRACTED", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/tests/static-assets.spec.ts", "source_location": "L1", "weight": 1.0}], "raw_calls": []}
@@ -0,0 +1 @@
{"nodes": [{"id": "home_agent_ai_agent_backend_workspace_2e709500_79ab_403f_9ca0_3f027ac9f36e_tests_e2e_critical_paths_spec_ts", "label": "e2e-critical-paths.spec.ts", "file_type": "code", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/tests/e2e-critical-paths.spec.ts", "source_location": "L1"}], "edges": [{"source": "home_agent_ai_agent_backend_workspace_2e709500_79ab_403f_9ca0_3f027ac9f36e_tests_e2e_critical_paths_spec_ts", "target": "test", "relation": "imports_from", "context": "import", "confidence": "EXTRACTED", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/tests/e2e-critical-paths.spec.ts", "source_location": "L1", "weight": 1.0}], "raw_calls": []}
@@ -0,0 +1 @@
{"nodes": [{"id": "home_agent_ai_agent_backend_workspace_2e709500_79ab_403f_9ca0_3f027ac9f36e_playwright_config_ts", "label": "playwright.config.ts", "file_type": "code", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/playwright.config.ts", "source_location": "L1"}], "edges": [{"source": "home_agent_ai_agent_backend_workspace_2e709500_79ab_403f_9ca0_3f027ac9f36e_playwright_config_ts", "target": "test", "relation": "imports_from", "context": "import", "confidence": "EXTRACTED", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/playwright.config.ts", "source_location": "L1", "weight": 1.0}], "raw_calls": []}
@@ -0,0 +1 @@
{"nodes": [{"id": "home_agent_ai_agent_backend_workspace_2e709500_79ab_403f_9ca0_3f027ac9f36e_tests_cross_browser_spec_ts", "label": "cross-browser.spec.ts", "file_type": "code", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/tests/cross-browser.spec.ts", "source_location": "L1"}], "edges": [{"source": "home_agent_ai_agent_backend_workspace_2e709500_79ab_403f_9ca0_3f027ac9f36e_tests_cross_browser_spec_ts", "target": "test", "relation": "imports_from", "context": "import", "confidence": "EXTRACTED", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/tests/cross-browser.spec.ts", "source_location": "L1", "weight": 1.0}], "raw_calls": []}
@@ -0,0 +1 @@
{"nodes": [{"id": "home_agent_ai_agent_backend_workspace_2e709500_79ab_403f_9ca0_3f027ac9f36e_tailwind_config_mjs", "label": "tailwind.config.mjs", "file_type": "code", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/tailwind.config.mjs", "source_location": "L1"}], "edges": [{"source": "home_agent_ai_agent_backend_workspace_2e709500_79ab_403f_9ca0_3f027ac9f36e_tailwind_config_mjs", "target": "typography", "relation": "imports_from", "context": "import", "confidence": "EXTRACTED", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/tailwind.config.mjs", "source_location": "L1", "weight": 1.0}], "raw_calls": []}
@@ -0,0 +1 @@
{"nodes": [{"id": "home_agent_ai_agent_backend_workspace_2e709500_79ab_403f_9ca0_3f027ac9f36e_tests_pages_spec_ts", "label": "pages.spec.ts", "file_type": "code", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/tests/pages.spec.ts", "source_location": "L1"}], "edges": [{"source": "home_agent_ai_agent_backend_workspace_2e709500_79ab_403f_9ca0_3f027ac9f36e_tests_pages_spec_ts", "target": "test", "relation": "imports_from", "context": "import", "confidence": "EXTRACTED", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/tests/pages.spec.ts", "source_location": "L1", "weight": 1.0}], "raw_calls": []}
@@ -0,0 +1 @@
{"nodes": [{"id": "home_agent_ai_agent_backend_workspace_2e709500_79ab_403f_9ca0_3f027ac9f36e_tests_portfolio_spec_ts", "label": "portfolio.spec.ts", "file_type": "code", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/tests/portfolio.spec.ts", "source_location": "L1"}], "edges": [{"source": "home_agent_ai_agent_backend_workspace_2e709500_79ab_403f_9ca0_3f027ac9f36e_tests_portfolio_spec_ts", "target": "test", "relation": "imports_from", "context": "import", "confidence": "EXTRACTED", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/tests/portfolio.spec.ts", "source_location": "L1", "weight": 1.0}], "raw_calls": []}
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
{"nodes": [{"id": "home_agent_ai_agent_backend_workspace_2e709500_79ab_403f_9ca0_3f027ac9f36e_src_pages_api_health_json_ts", "label": "health.json.ts", "file_type": "code", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/src/pages/api/health.json.ts", "source_location": "L1"}, {"id": "api_health_json_get", "label": "GET()", "file_type": "code", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/src/pages/api/health.json.ts", "source_location": "L5"}], "edges": [{"source": "home_agent_ai_agent_backend_workspace_2e709500_79ab_403f_9ca0_3f027ac9f36e_src_pages_api_health_json_ts", "target": "astro", "relation": "imports_from", "context": "import", "confidence": "EXTRACTED", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/src/pages/api/health.json.ts", "source_location": "L1", "weight": 1.0}, {"source": "home_agent_ai_agent_backend_workspace_2e709500_79ab_403f_9ca0_3f027ac9f36e_src_pages_api_health_json_ts", "target": "api_health_json_get", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/src/pages/api/health.json.ts", "source_location": "L5", "weight": 1.0}], "raw_calls": [{"caller_nid": "api_health_json_get", "callee": "floor", "is_member_call": true, "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/src/pages/api/health.json.ts", "source_location": "L6"}, {"caller_nid": "api_health_json_get", "callee": "now", "is_member_call": true, "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/src/pages/api/health.json.ts", "source_location": "L6"}, {"caller_nid": "api_health_json_get", "callee": "memoryUsage", "is_member_call": true, "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/src/pages/api/health.json.ts", "source_location": "L7"}, {"caller_nid": "api_health_json_get", "callee": "toISOString", "is_member_call": true, "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/src/pages/api/health.json.ts", "source_location": "L11"}, {"caller_nid": "api_health_json_get", "callee": "round", "is_member_call": true, "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/src/pages/api/health.json.ts", "source_location": "L18"}, {"caller_nid": "api_health_json_get", "callee": "round", "is_member_call": true, "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/src/pages/api/health.json.ts", "source_location": "L19"}, {"caller_nid": "api_health_json_get", "callee": "round", "is_member_call": true, "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/src/pages/api/health.json.ts", "source_location": "L20"}, {"caller_nid": "api_health_json_get", "callee": "stringify", "is_member_call": true, "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/src/pages/api/health.json.ts", "source_location": "L27"}]}
@@ -0,0 +1 @@
{"nodes": [{"id": "home_agent_ai_agent_backend_workspace_2e709500_79ab_403f_9ca0_3f027ac9f36e_tests_blog_spec_ts", "label": "blog.spec.ts", "file_type": "code", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/tests/blog.spec.ts", "source_location": "L1"}], "edges": [{"source": "home_agent_ai_agent_backend_workspace_2e709500_79ab_403f_9ca0_3f027ac9f36e_tests_blog_spec_ts", "target": "test", "relation": "imports_from", "context": "import", "confidence": "EXTRACTED", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/tests/blog.spec.ts", "source_location": "L1", "weight": 1.0}], "raw_calls": []}
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
{"nodes": [{"id": "home_agent_ai_agent_backend_workspace_2e709500_79ab_403f_9ca0_3f027ac9f36e_src_middleware_ts", "label": "middleware.ts", "file_type": "code", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/src/middleware.ts", "source_location": "L1"}, {"id": "src_middleware_validatecsrforigin", "label": "validateCsrfOrigin()", "file_type": "code", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/src/middleware.ts", "source_location": "L19"}], "edges": [{"source": "home_agent_ai_agent_backend_workspace_2e709500_79ab_403f_9ca0_3f027ac9f36e_src_middleware_ts", "target": "astro_middleware", "relation": "imports_from", "context": "import", "confidence": "EXTRACTED", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/src/middleware.ts", "source_location": "L1", "weight": 1.0}, {"source": "home_agent_ai_agent_backend_workspace_2e709500_79ab_403f_9ca0_3f027ac9f36e_src_middleware_ts", "target": "home_agent_ai_agent_backend_workspace_2e709500_79ab_403f_9ca0_3f027ac9f36e_src_utils_logger_ts", "relation": "imports_from", "context": "import", "confidence": "EXTRACTED", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/src/middleware.ts", "source_location": "L2", "weight": 1.0}, {"source": "home_agent_ai_agent_backend_workspace_2e709500_79ab_403f_9ca0_3f027ac9f36e_src_middleware_ts", "target": "utils_logger_logger", "relation": "imports", "context": "import", "confidence": "EXTRACTED", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/src/middleware.ts", "source_location": "L2", "weight": 1.0}, {"source": "home_agent_ai_agent_backend_workspace_2e709500_79ab_403f_9ca0_3f027ac9f36e_src_middleware_ts", "target": "home_agent_ai_agent_backend_workspace_2e709500_79ab_403f_9ca0_3f027ac9f36e_src_utils_sentry_ts", "relation": "imports_from", "context": "import", "confidence": "EXTRACTED", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/src/middleware.ts", "source_location": "L3", "weight": 1.0}, {"source": "home_agent_ai_agent_backend_workspace_2e709500_79ab_403f_9ca0_3f027ac9f36e_src_middleware_ts", "target": "utils_sentry_captureexception", "relation": "imports", "context": "import", "confidence": "EXTRACTED", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/src/middleware.ts", "source_location": "L3", "weight": 1.0}, {"source": "home_agent_ai_agent_backend_workspace_2e709500_79ab_403f_9ca0_3f027ac9f36e_src_middleware_ts", "target": "src_middleware_validatecsrforigin", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/src/middleware.ts", "source_location": "L19", "weight": 1.0}], "raw_calls": [{"caller_nid": "src_middleware_validatecsrforigin", "callee": "includes", "is_member_call": true, "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/src/middleware.ts", "source_location": "L24"}, {"caller_nid": "src_middleware_validatecsrforigin", "callee": "startsWith", "is_member_call": true, "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/src/middleware.ts", "source_location": "L25"}, {"caller_nid": "src_middleware_validatecsrforigin", "callee": "get", "is_member_call": true, "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/src/middleware.ts", "source_location": "L36"}, {"caller_nid": "src_middleware_validatecsrforigin", "callee": "get", "is_member_call": true, "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/src/middleware.ts", "source_location": "L37"}, {"caller_nid": "src_middleware_validatecsrforigin", "callee": "startsWith", "is_member_call": true, "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/src/middleware.ts", "source_location": "L52"}, {"caller_nid": "src_middleware_validatecsrforigin", "callee": "startsWith", "is_member_call": true, "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/src/middleware.ts", "source_location": "L52"}]}
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
{"nodes": [{"id": "home_agent_ai_agent_backend_workspace_2e709500_79ab_403f_9ca0_3f027ac9f36e_tests_destructive_chaos_spec_ts", "label": "destructive-chaos.spec.ts", "file_type": "code", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/tests/destructive-chaos.spec.ts", "source_location": "L1"}], "edges": [{"source": "home_agent_ai_agent_backend_workspace_2e709500_79ab_403f_9ca0_3f027ac9f36e_tests_destructive_chaos_spec_ts", "target": "test", "relation": "imports_from", "context": "import", "confidence": "EXTRACTED", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/tests/destructive-chaos.spec.ts", "source_location": "L1", "weight": 1.0}], "raw_calls": []}
@@ -0,0 +1 @@
{"nodes": [{"id": "home_agent_ai_agent_backend_workspace_2e709500_79ab_403f_9ca0_3f027ac9f36e_tests_e2e_blog_navigation_spec_ts", "label": "e2e-blog-navigation.spec.ts", "file_type": "code", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/tests/e2e-blog-navigation.spec.ts", "source_location": "L1"}], "edges": [{"source": "home_agent_ai_agent_backend_workspace_2e709500_79ab_403f_9ca0_3f027ac9f36e_tests_e2e_blog_navigation_spec_ts", "target": "test", "relation": "imports_from", "context": "import", "confidence": "EXTRACTED", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/tests/e2e-blog-navigation.spec.ts", "source_location": "L1", "weight": 1.0}], "raw_calls": []}
@@ -0,0 +1 @@
{"nodes": [{"id": "home_agent_ai_agent_backend_workspace_2e709500_79ab_403f_9ca0_3f027ac9f36e_tests_contact_form_spec_ts", "label": "contact-form.spec.ts", "file_type": "code", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/tests/contact-form.spec.ts", "source_location": "L1"}], "edges": [{"source": "home_agent_ai_agent_backend_workspace_2e709500_79ab_403f_9ca0_3f027ac9f36e_tests_contact_form_spec_ts", "target": "test", "relation": "imports_from", "context": "import", "confidence": "EXTRACTED", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/tests/contact-form.spec.ts", "source_location": "L1", "weight": 1.0}], "raw_calls": []}
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
{"nodes": [{"id": "home_agent_ai_agent_backend_workspace_2e709500_79ab_403f_9ca0_3f027ac9f36e_astro_config_mjs", "label": "astro.config.mjs", "file_type": "code", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/astro.config.mjs", "source_location": "L1"}], "edges": [{"source": "home_agent_ai_agent_backend_workspace_2e709500_79ab_403f_9ca0_3f027ac9f36e_astro_config_mjs", "target": "config", "relation": "imports_from", "context": "import", "confidence": "EXTRACTED", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/astro.config.mjs", "source_location": "L1", "weight": 1.0}, {"source": "home_agent_ai_agent_backend_workspace_2e709500_79ab_403f_9ca0_3f027ac9f36e_astro_config_mjs", "target": "tailwind", "relation": "imports_from", "context": "import", "confidence": "EXTRACTED", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/astro.config.mjs", "source_location": "L2", "weight": 1.0}, {"source": "home_agent_ai_agent_backend_workspace_2e709500_79ab_403f_9ca0_3f027ac9f36e_astro_config_mjs", "target": "node", "relation": "imports_from", "context": "import", "confidence": "EXTRACTED", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/astro.config.mjs", "source_location": "L3", "weight": 1.0}], "raw_calls": []}
@@ -0,0 +1 @@
{"nodes": [{"id": "home_agent_ai_agent_backend_workspace_2e709500_79ab_403f_9ca0_3f027ac9f36e_src_content_config_ts", "label": "config.ts", "file_type": "code", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/src/content/config.ts", "source_location": "L1"}], "edges": [{"source": "home_agent_ai_agent_backend_workspace_2e709500_79ab_403f_9ca0_3f027ac9f36e_src_content_config_ts", "target": "astro_content", "relation": "imports_from", "context": "import", "confidence": "EXTRACTED", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/src/content/config.ts", "source_location": "L1", "weight": 1.0}], "raw_calls": []}
@@ -0,0 +1 @@
{"nodes": [{"id": "home_agent_ai_agent_backend_workspace_2e709500_79ab_403f_9ca0_3f027ac9f36e_src_pages_sitemap_index_xml_ts", "label": "sitemap-index.xml.ts", "file_type": "code", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/src/pages/sitemap-index.xml.ts", "source_location": "L1"}, {"id": "pages_sitemap_index_xml_get", "label": "GET()", "file_type": "code", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/src/pages/sitemap-index.xml.ts", "source_location": "L10"}], "edges": [{"source": "home_agent_ai_agent_backend_workspace_2e709500_79ab_403f_9ca0_3f027ac9f36e_src_pages_sitemap_index_xml_ts", "target": "astro", "relation": "imports_from", "context": "import", "confidence": "EXTRACTED", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/src/pages/sitemap-index.xml.ts", "source_location": "L1", "weight": 1.0}, {"source": "home_agent_ai_agent_backend_workspace_2e709500_79ab_403f_9ca0_3f027ac9f36e_src_pages_sitemap_index_xml_ts", "target": "pages_sitemap_index_xml_get", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/src/pages/sitemap-index.xml.ts", "source_location": "L10", "weight": 1.0}], "raw_calls": [{"caller_nid": "pages_sitemap_index_xml_get", "callee": "toISOString", "is_member_call": true, "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/src/pages/sitemap-index.xml.ts", "source_location": "L11"}]}
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
{"nodes": [{"id": "home_agent_ai_agent_backend_workspace_2e709500_79ab_403f_9ca0_3f027ac9f36e_tests_e2e_smoke_suite_spec_ts", "label": "e2e-smoke-suite.spec.ts", "file_type": "code", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/tests/e2e-smoke-suite.spec.ts", "source_location": "L1"}], "edges": [{"source": "home_agent_ai_agent_backend_workspace_2e709500_79ab_403f_9ca0_3f027ac9f36e_tests_e2e_smoke_suite_spec_ts", "target": "test", "relation": "imports_from", "context": "import", "confidence": "EXTRACTED", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/tests/e2e-smoke-suite.spec.ts", "source_location": "L1", "weight": 1.0}], "raw_calls": []}
@@ -0,0 +1 @@
{"nodes": [{"id": "home_agent_ai_agent_backend_workspace_2e709500_79ab_403f_9ca0_3f027ac9f36e_scripts_backup_windows_tasks_ps1", "label": "windows-tasks.ps1", "file_type": "code", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/scripts/backup/windows-tasks.ps1", "source_location": "L1"}], "edges": [], "raw_calls": []}
@@ -0,0 +1 @@
{"nodes": [{"id": "home_agent_ai_agent_backend_workspace_2e709500_79ab_403f_9ca0_3f027ac9f36e_src_env_d_ts", "label": "env.d.ts", "file_type": "code", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/src/env.d.ts", "source_location": "L1"}], "edges": [], "raw_calls": []}
@@ -0,0 +1 @@
{"nodes": [{"id": "home_agent_ai_agent_backend_workspace_2e709500_79ab_403f_9ca0_3f027ac9f36e_tests_security_headers_test_ts", "label": "security-headers.test.ts", "file_type": "code", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/tests/security-headers.test.ts", "source_location": "L1"}], "edges": [{"source": "home_agent_ai_agent_backend_workspace_2e709500_79ab_403f_9ca0_3f027ac9f36e_tests_security_headers_test_ts", "target": "test", "relation": "imports_from", "context": "import", "confidence": "EXTRACTED", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/tests/security-headers.test.ts", "source_location": "L6", "weight": 1.0}], "raw_calls": []}
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
{"nodes": [{"id": "home_agent_ai_agent_backend_workspace_2e709500_79ab_403f_9ca0_3f027ac9f36e_tests_newsletter_subscription_spec_ts", "label": "newsletter-subscription.spec.ts", "file_type": "code", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/tests/newsletter-subscription.spec.ts", "source_location": "L1"}], "edges": [{"source": "home_agent_ai_agent_backend_workspace_2e709500_79ab_403f_9ca0_3f027ac9f36e_tests_newsletter_subscription_spec_ts", "target": "test", "relation": "imports_from", "context": "import", "confidence": "EXTRACTED", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/tests/newsletter-subscription.spec.ts", "source_location": "L1", "weight": 1.0}], "raw_calls": []}
@@ -0,0 +1 @@
{"nodes": [{"id": "home_agent_ai_agent_backend_workspace_2e709500_79ab_403f_9ca0_3f027ac9f36e_tests_accessibility_spec_ts", "label": "accessibility.spec.ts", "file_type": "code", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/tests/accessibility.spec.ts", "source_location": "L1"}], "edges": [{"source": "home_agent_ai_agent_backend_workspace_2e709500_79ab_403f_9ca0_3f027ac9f36e_tests_accessibility_spec_ts", "target": "test", "relation": "imports_from", "context": "import", "confidence": "EXTRACTED", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/tests/accessibility.spec.ts", "source_location": "L1", "weight": 1.0}], "raw_calls": []}
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+7734 -912
View File
File diff suppressed because it is too large Load Diff
+187
View File
@@ -0,0 +1,187 @@
---
// MobileStickyCtaBar — Sticky bottom action bar for mobile/tablet (<768px)
// 3 equal columns: Call, WhatsApp, Quote
// - Fixed to bottom of viewport
// - Respects safe-area-inset-bottom on iOS
// - Hidden at md+ breakpoints (>=768px)
// - Hidden on print
// - Light + dark theme aware
---
<nav
aria-label="Quick contact actions"
data-testid="mobile-sticky-cta-bar"
class="mobile-sticky-cta md:hidden"
>
<a
href="tel:+919561417403"
aria-label="Call WorkRoot"
data-testid="mobile-cta-call"
class="mobile-sticky-cta__btn"
>
<svg
class="mobile-sticky-cta__icon"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
aria-hidden="true"
focusable="false"
>
<path d="M22 16.92v3a2 2 0 0 1-2.18 2 19.86 19.86 0 0 1-8.63-3.07 19.5 19.5 0 0 1-6-6A19.86 19.86 0 0 1 2.12 4.18 2 2 0 0 1 4.11 2h3a2 2 0 0 1 2 1.72c.13.96.37 1.9.72 2.81a2 2 0 0 1-.45 2.11L8.09 9.91a16 16 0 0 0 6 6l1.27-1.27a2 2 0 0 1 2.11-.45c.91.35 1.85.59 2.81.72A2 2 0 0 1 22 16.92z"/>
</svg>
<span class="mobile-sticky-cta__label">Call</span>
</a>
<a
href="https://wa.me/919561417403?text=Hi%20WorkRoot%2C%20I%27d%20like%20to%20discuss%20a%20project."
target="_blank"
rel="noopener"
aria-label="Chat on WhatsApp"
data-testid="mobile-cta-whatsapp"
class="mobile-sticky-cta__btn mobile-sticky-cta__btn--whatsapp"
>
<svg
class="mobile-sticky-cta__icon"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="currentColor"
aria-hidden="true"
focusable="false"
>
<path d="M17.472 14.382c-.297-.149-1.758-.867-2.03-.967-.273-.099-.471-.148-.67.15-.197.297-.767.966-.94 1.164-.173.199-.347.223-.644.075-.297-.15-1.255-.463-2.39-1.475-.883-.788-1.48-1.761-1.653-2.059-.173-.297-.018-.458.13-.606.134-.133.298-.347.446-.52.149-.174.198-.298.298-.497.099-.198.05-.371-.025-.52-.075-.149-.669-1.612-.916-2.207-.242-.579-.487-.5-.669-.51-.173-.008-.371-.01-.57-.01-.198 0-.52.074-.792.372-.272.297-1.04 1.016-1.04 2.479 0 1.462 1.065 2.875 1.213 3.074.149.198 2.096 3.2 5.077 4.487.709.306 1.262.489 1.694.625.712.227 1.36.195 1.871.118.571-.085 1.758-.719 2.006-1.413.248-.694.248-1.289.173-1.413-.074-.124-.272-.198-.57-.347m-5.421 7.403h-.004a9.87 9.87 0 01-5.031-1.378l-.361-.214-3.741.982.998-3.648-.235-.374a9.86 9.86 0 01-1.51-5.26c.001-5.45 4.436-9.884 9.888-9.884 2.64 0 5.122 1.03 6.988 2.898a9.825 9.825 0 012.893 6.994c-.003 5.45-4.437 9.884-9.885 9.884m8.413-18.297A11.815 11.815 0 0012.05 0C5.495 0 .16 5.335.157 11.892c0 2.096.547 4.142 1.588 5.945L.057 24l6.305-1.654a11.882 11.882 0 005.683 1.448h.005c6.554 0 11.89-5.335 11.893-11.893A11.821 11.821 0 0020.464 3.488"/>
</svg>
<span class="mobile-sticky-cta__label">WhatsApp</span>
</a>
<a
href="/contact"
aria-label="Request a quote"
data-testid="mobile-cta-quote"
class="mobile-sticky-cta__btn mobile-sticky-cta__btn--accent"
>
<svg
class="mobile-sticky-cta__icon"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
aria-hidden="true"
focusable="false"
>
<path d="M4 4h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2z"/>
<polyline points="22,6 12,13 2,6"/>
</svg>
<span class="mobile-sticky-cta__label">Quote</span>
</a>
</nav>
<style>
.mobile-sticky-cta {
position: fixed;
left: 0;
right: 0;
bottom: 0;
z-index: 50;
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
gap: 0;
background-color: #ffffff;
border-top: 1px solid rgba(15, 23, 42, 0.08);
box-shadow: 0 -8px 20px -8px rgba(15, 23, 42, 0.18);
padding-bottom: env(safe-area-inset-bottom, 0px);
/* Ensure no horizontal overflow on very narrow viewports */
max-width: 100vw;
overflow: hidden;
}
:global(html.dark) .mobile-sticky-cta {
background-color: #0f172a;
border-top-color: rgba(255, 255, 255, 0.08);
box-shadow: 0 -8px 20px -8px rgba(0, 0, 0, 0.55);
}
.mobile-sticky-cta__btn {
display: inline-flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 2px;
min-height: 56px;
padding: 8px 6px;
text-decoration: none;
color: #0f172a;
font-size: 12px;
font-weight: 600;
line-height: 1.1;
background-color: transparent;
transition: background-color 150ms ease, color 150ms ease;
-webkit-tap-highlight-color: transparent;
}
:global(html.dark) .mobile-sticky-cta__btn {
color: #e2e8f0;
}
.mobile-sticky-cta__btn:active {
background-color: rgba(8, 145, 178, 0.08);
}
:global(html.dark) .mobile-sticky-cta__btn:active {
background-color: rgba(34, 211, 238, 0.12);
}
.mobile-sticky-cta__btn:focus-visible {
outline: 2px solid #0891b2;
outline-offset: -2px;
}
.mobile-sticky-cta__btn--whatsapp {
color: #128c4f;
}
:global(html.dark) .mobile-sticky-cta__btn--whatsapp {
color: #25D366;
}
.mobile-sticky-cta__btn--accent {
color: #b45309;
}
:global(html.dark) .mobile-sticky-cta__btn--accent {
color: #fbbf24;
}
.mobile-sticky-cta__icon {
width: 22px;
height: 22px;
flex-shrink: 0;
}
.mobile-sticky-cta__label {
display: block;
font-size: 11px;
letter-spacing: 0.01em;
}
/* Hide on tablets/desktops (>=768px = Tailwind md breakpoint).
Note: the `md:hidden` utility on the element loses the cascade against
this Astro-scoped class selector (`[data-astro-cid-*]` raises specificity),
so we enforce the breakpoint here too. */
@media (min-width: 768px) {
.mobile-sticky-cta {
display: none;
}
}
/* Hide on print */
@media print {
.mobile-sticky-cta {
display: none !important;
}
}
</style>
+104
View File
@@ -0,0 +1,104 @@
---
// WhatsAppFab — Global floating WhatsApp contact button
// - Fixed bottom-right, 56x56, brand green (#25D366)
// - Visible on every page across desktop and mobile
// - Mobile: lifted above the MobileStickyCtaBar via safe CSS variable offset
// - Respects iOS safe-area-inset-bottom
// - Hidden when printing
---
<a
href="https://wa.me/919561417403?text=Hi%20WorkRoot%2C%20I%27d%20like%20to%20discuss%20a%20project."
target="_blank"
rel="noopener"
aria-label="Chat on WhatsApp"
data-testid="whatsapp-fab"
class="whatsapp-fab"
>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="currentColor"
class="whatsapp-fab__icon"
aria-hidden="true"
focusable="false"
>
<path d="M17.472 14.382c-.297-.149-1.758-.867-2.03-.967-.273-.099-.471-.148-.67.15-.197.297-.767.966-.94 1.164-.173.199-.347.223-.644.075-.297-.15-1.255-.463-2.39-1.475-.883-.788-1.48-1.761-1.653-2.059-.173-.297-.018-.458.13-.606.134-.133.298-.347.446-.52.149-.174.198-.298.298-.497.099-.198.05-.371-.025-.52-.075-.149-.669-1.612-.916-2.207-.242-.579-.487-.5-.669-.51-.173-.008-.371-.01-.57-.01-.198 0-.52.074-.792.372-.272.297-1.04 1.016-1.04 2.479 0 1.462 1.065 2.875 1.213 3.074.149.198 2.096 3.2 5.077 4.487.709.306 1.262.489 1.694.625.712.227 1.36.195 1.871.118.571-.085 1.758-.719 2.006-1.413.248-.694.248-1.289.173-1.413-.074-.124-.272-.198-.57-.347m-5.421 7.403h-.004a9.87 9.87 0 01-5.031-1.378l-.361-.214-3.741.982.998-3.648-.235-.374a9.86 9.86 0 01-1.51-5.26c.001-5.45 4.436-9.884 9.888-9.884 2.64 0 5.122 1.03 6.988 2.898a9.825 9.825 0 012.893 6.994c-.003 5.45-4.437 9.884-9.885 9.884m8.413-18.297A11.815 11.815 0 0012.05 0C5.495 0 .16 5.335.157 11.892c0 2.096.547 4.142 1.588 5.945L.057 24l6.305-1.654a11.882 11.882 0 005.683 1.448h.005c6.554 0 11.89-5.335 11.893-11.893A11.821 11.821 0 0020.464 3.488"/>
</svg>
</a>
<style>
.whatsapp-fab {
/* Mobile: lifted above the sticky CTA bar so they don't overlap. */
--wa-fab-bottom: calc(72px + env(safe-area-inset-bottom, 0px) + 16px);
position: fixed;
right: 16px;
right: max(16px, env(safe-area-inset-right, 16px));
bottom: var(--wa-fab-bottom);
width: 56px;
height: 56px;
display: inline-flex;
align-items: center;
justify-content: center;
border-radius: 9999px;
background-color: #25D366;
color: #ffffff;
box-shadow:
0 6px 16px rgba(37, 211, 102, 0.35),
0 2px 6px rgba(0, 0, 0, 0.18);
z-index: 60;
text-decoration: none;
transition: transform 200ms ease, box-shadow 200ms ease, background-color 200ms ease;
-webkit-tap-highlight-color: transparent;
}
.whatsapp-fab:hover {
background-color: #1ebe5c;
transform: translateY(-2px);
box-shadow:
0 10px 20px rgba(37, 211, 102, 0.45),
0 4px 8px rgba(0, 0, 0, 0.22);
}
.whatsapp-fab:focus-visible {
outline: 2px solid #ffffff;
outline-offset: 3px;
box-shadow:
0 0 0 4px rgba(37, 211, 102, 0.55),
0 6px 16px rgba(37, 211, 102, 0.4);
}
.whatsapp-fab:active {
transform: translateY(0);
}
.whatsapp-fab__icon {
width: 28px;
height: 28px;
}
/* Desktop / tablet: no sticky CTA bar present, sit at standard offset. */
@media (min-width: 768px) {
.whatsapp-fab {
--wa-fab-bottom: calc(24px + env(safe-area-inset-bottom, 0px));
}
}
/* Reduce motion */
@media (prefers-reduced-motion: reduce) {
.whatsapp-fab {
transition: none;
}
.whatsapp-fab:hover {
transform: none;
}
}
/* Hide on print */
@media print {
.whatsapp-fab {
display: none !important;
}
}
</style>
+11
View File
@@ -6,6 +6,8 @@ import Header from '../components/Header.astro';
import Footer from '../components/Footer.astro';
import Analytics from '../components/Analytics.astro';
import PWAInstallPrompt from '../components/PWAInstallPrompt.astro';
import WhatsAppFab from '../components/WhatsAppFab.astro';
import MobileStickyCtaBar from '../components/MobileStickyCtaBar.astro';
import '../styles/global.css';
interface Props {
@@ -290,6 +292,15 @@ const fullTitle = title === 'Home' ? siteName : `${title} | ${siteName}`;
<!-- Footer -->
<Footer />
<!-- Spacer to ensure footer/content clears the mobile sticky CTA bar (<md only) -->
<div aria-hidden="true" class="md:hidden h-[calc(64px+env(safe-area-inset-bottom,0px))] w-full"></div>
<!-- Mobile sticky bottom CTA bar (visible <md) -->
<MobileStickyCtaBar />
<!-- Floating WhatsApp action (global) -->
<WhatsAppFab />
<!-- Scripts slot for page-specific scripts -->
<slot name="scripts" />
+88 -15
View File
@@ -122,6 +122,43 @@ const stats = [
{ value: 3, suffix: '', label: 'Team Members', icon: 'clock' },
];
// Technology stack data — relocated below the fold so social proof leads the
// hero. Each entry pairs the framework name with a short context line and a
// neutral monochrome SVG icon (uses currentColor so the grayscale →
// brand-color hover treatment is driven entirely by Tailwind utility classes).
const techStack = [
{
name: 'Angular',
context: 'Enterprise front-ends',
icon: '<svg viewBox="0 0 24 24" class="w-8 h-8" fill="currentColor" aria-hidden="true"><path d="M12 2.5 2.5 5.7l1.6 12 7.9 3.8 7.9-3.8 1.6-12L12 2.5Zm0 2.3 6.3 11.3h-1.9l-1.3-3.1H9l-1.3 3.1H5.7L12 4.8Zm-2.3 6.5h4.6L12 7.3l-2.3 4Z"/></svg>',
},
{
name: 'Spring Boot',
context: 'Backend APIs',
icon: '<svg viewBox="0 0 24 24" class="w-8 h-8" fill="currentColor" aria-hidden="true"><path d="M21.6 4.2c-.4 1-1 1.9-1.5 2.7C18.4 4.5 15.4 3 12 3 6.5 3 2 7.5 2 13s4.5 10 10 10c5 0 9.1-3.7 9.9-8.5.5-2.4.1-5-1.3-7.4l1-2.9Zm-9.6 17c-4.6 0-8.4-3.8-8.4-8.4 0-3.4 2.1-6.4 5.1-7.7-1 1.6-1.7 3.4-2.1 5.3l-.1.6 1.1.4c2.4.8 4.8 1.8 7 3.6 1.4 1.1 2.4 2.6 2.8 4.3-1.4 1.2-3.3 1.9-5.4 1.9Zm6.3-2.5c-.8-2.5-3-4.8-6.5-6.5-1.2-.6-2.5-1.1-3.7-1.4.5-2.1 1.5-4 2.9-5.5 1.1-.1 2.2 0 3.3.4 1.8.6 3.4 1.8 4.6 3.4l-.2.3-.1.1c-.8 1.4-1.4 3-1.7 4.6-.5 1.7-.7 3.4-.5 4.6h1.9Z"/></svg>',
},
{
name: 'Next.js',
context: 'React SSR & ISR',
icon: '<svg viewBox="0 0 24 24" class="w-8 h-8" fill="currentColor" aria-hidden="true"><path d="M12 2C6.5 2 2 6.5 2 12s4.5 10 10 10 10-4.5 10-10S17.5 2 12 2Zm-.8 4.5h1.6v9.2H11.3l-3.5-5v5H6.2V6.5h1.5l3.5 5v-5Zm5.9 0h1.5v11h-1.5v-11Z"/></svg>',
},
{
name: 'Astro',
context: 'Marketing & docs sites',
icon: '<svg viewBox="0 0 24 24" class="w-8 h-8" fill="currentColor" aria-hidden="true"><path d="M8.4 17.8c-.4-1.4-.5-2.7 0-3.4l.1-.1c.4 1.1 1.4 1.6 2.7 2 2 .6 2.6 1.4 1.8 3.1l-.1.2c-.6 1.1-1.8 2-2.6 2-.7 0-1.3-.5-1.6-1.4 0-.3-.1-.6-.1-1l-.2-1.4ZM14.8 2 18 13H6L9.2 2h5.6Zm-2.8 2.6L9.7 12h4.6L12 4.6Z"/></svg>',
},
{
name: 'Golang',
context: 'High-throughput services',
icon: '<svg viewBox="0 0 24 24" class="w-8 h-8" fill="currentColor" aria-hidden="true"><path d="M2.4 11.6c-.1 0-.1 0 0 0-.1-.1-.1-.1 0-.2l.5-.6c.1-.1.2-.1.3-.1H7c.1 0 .1.1.1.2l-.4.5c-.1.1-.2.2-.3.2l-4 .1v-.1Zm-1.9 1.2c-.1 0-.1-.1 0-.2l.5-.6c.1-.1.2-.1.3-.1h5.4c.1 0 .2.1.1.2l-.2.5c-.1.1-.2.2-.3.2l-5.8 0Zm3.1 1.1c-.1 0-.1-.1 0-.2l.4-.5c.1-.1.2-.1.3-.1h2.4c.1 0 .2.1.2.2l-.1.5c0 .1-.1.2-.2.2l-3-.1Zm12.6-2.6c-.8.2-1.4.4-2.2.6-.2 0-.2 0-.4-.1-.2-.2-.3-.4-.6-.5-.8-.4-1.6-.3-2.4.2-.9.6-1.4 1.4-1.4 2.5 0 1 .7 1.9 1.7 2 .9.1 1.6-.2 2.2-.9.1-.2.2-.3.4-.5h-3.1c-.3 0-.4-.2-.3-.5l.4-.7c.1-.2.3-.3.5-.3h5.8c0 .1 0 .1 0 .2-.2 1.2-.7 2.2-1.5 3.1-1 1-2.3 1.7-3.7 1.8-2.5.3-4.7-.7-5.7-3-.8-1.9-.5-3.7.6-5.4 1.2-1.8 3-2.8 5.2-3.2 1.8-.3 3.5-.1 5 1 1.1.8 1.7 1.9 2 3.2 0 .2 0 .3-.3.3Zm3.5 5.4c-1.6 0-3-.5-4.2-1.7-1-1-1.6-2.2-1.7-3.6-.2-2.1.4-3.9 1.8-5.4 1.5-1.6 3.3-2.5 5.4-2.5 2 0 3.5.7 4.6 2.2.9 1.4 1.2 2.9 1 4.5-.4 3.7-3.2 6.2-6.9 6.5Zm3.9-6.6c0-.3-.1-.5-.1-.8-.2-1.4-1.4-2-2.7-1.7-1.3.3-2.1 1.1-2.4 2.4-.2 1.1.3 2.2 1.3 2.6.8.4 1.6.3 2.3-.2 1.1-.6 1.6-1.5 1.6-2.3Z"/></svg>',
},
{
name: 'PostgreSQL',
context: 'Production data layer',
icon: '<svg viewBox="0 0 24 24" class="w-8 h-8" fill="currentColor" aria-hidden="true"><path d="M12 3c-3.3 0-6 1-6 2.3v13.5c0 1.3 2.7 2.3 6 2.3s6-1 6-2.3V5.3C18 4 15.3 3 12 3Zm0 1.5c2.9 0 5 .8 5 1.5s-2.1 1.5-5 1.5-5-.8-5-1.5 2.1-1.5 5-1.5Zm5 14.3c0 .7-2.1 1.5-5 1.5s-5-.8-5-1.5v-2.4c1.2.7 3 1.1 5 1.1s3.8-.4 5-1.1v2.4Zm0-4.6c0 .7-2.1 1.5-5 1.5s-5-.8-5-1.5v-2.4c1.2.7 3 1.1 5 1.1s3.8-.4 5-1.1v2.4Zm0-4.6c0 .7-2.1 1.5-5 1.5s-5-.8-5-1.5V7.7c1.2.7 3 1.1 5 1.1s3.8-.4 5-1.1v2.4Z"/></svg>',
},
];
// FAQ data
const faqs = [
{
@@ -233,24 +270,14 @@ const faqs = [
<span>No commitment</span>
</p>
<!-- Tech stack proof strip -->
<div class="mt-16 opacity-0 animate-slide-up" style="animation-delay: 1s;">
<p class="text-sm text-secondary-400 uppercase tracking-wider mb-8">Powered By Industry Leaders</p>
<div class="flex flex-wrap justify-center items-center gap-12 opacity-60" role="list" aria-label="Technologies we use">
<div class="text-2xl font-bold text-white" role="listitem">Angular</div>
<div class="text-2xl font-bold text-white" role="listitem">Spring Boot</div>
<div class="text-2xl font-bold text-white" role="listitem">Next.js</div>
<div class="text-2xl font-bold text-white" role="listitem">Astro</div>
<div class="text-2xl font-bold text-white" role="listitem">Golang</div>
</div>
</div>
</div>
</div>
<!-- Scroll indicator -->
<div class="absolute bottom-8 left-1/2 -translate-x-1/2 opacity-0 animate-fade-in" style="animation-delay: 1.2s;">
<a href="#client-logos" class="flex flex-col items-center text-white/60 hover:text-white transition-colors group" aria-label="Scroll to clients">
<span class="text-sm mb-2">Discover More</span>
<!-- Scroll indicator — points at the client-logo social-proof strip just
below the hero so the first interaction surface is trust, not stack. -->
<div class="absolute bottom-8 left-1/2 -translate-x-1/2 opacity-0 animate-fade-in" style="animation-delay: 1s;">
<a href="#client-logos" class="flex flex-col items-center text-white/60 hover:text-white transition-colors group" aria-label="Scroll to client logos">
<span class="text-sm mb-2">See who we work with</span>
<svg class="w-6 h-6 animate-bounce" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 14l-7 7m0 0l-7-7m7 7V3"></path>
</svg>
@@ -635,6 +662,52 @@ const faqs = [
</div>
</section>
<!-- Technologies We Work With — relocated from the hero so social proof
leads above the fold. Kept intentionally subdued (border-only cards,
neutral grayscale icons) so it reads as supporting context rather than
competing with the client-logo trust strip up top. -->
<section id="tech-stack" class="py-20 sm:py-24 bg-secondary-50 dark:bg-secondary-900/60 border-y border-secondary-100 dark:border-secondary-800">
<div class="container-wrapper">
<div class="text-center max-w-3xl mx-auto mb-12 sm:mb-16" data-animate="fade-up">
<span class="inline-block px-4 py-2 rounded-full bg-secondary-100 dark:bg-secondary-800 text-secondary-700 dark:text-secondary-300 text-xs sm:text-sm font-bold uppercase tracking-wider mb-4">Tech Stack</span>
<h2 class="text-3xl sm:text-4xl lg:text-5xl font-bold text-secondary-900 dark:text-secondary-100 mb-4 sm:mb-6">Technologies we work with</h2>
<p class="text-base sm:text-xl text-secondary-600 dark:text-secondary-400">
Battle-tested frameworks and runtimes we ship to production across enterprise, university, and government deployments.
</p>
</div>
<ul
class="grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-6 gap-3 sm:gap-4 max-w-6xl mx-auto"
role="list"
aria-label="Technologies we work with"
>
{techStack.map((tech, index) => (
<li
class="group flex flex-col items-center justify-center text-center gap-2 sm:gap-3 rounded-2xl border border-secondary-200 dark:border-secondary-700 bg-white dark:bg-secondary-800/70 px-3 py-5 sm:px-4 sm:py-6 min-w-0 transition-all duration-300 hover:-translate-y-1 hover:border-primary-300 hover:shadow-lg dark:hover:border-primary-500/60 focus-within:ring-2 focus-within:ring-primary-400"
data-animate="fade-up"
data-delay={String(index * 60)}
>
<span
class="text-secondary-400 dark:text-secondary-500 group-hover:text-primary-500 dark:group-hover:text-primary-400 transition-colors"
aria-hidden="true"
set:html={tech.icon}
/>
<span class="text-sm sm:text-base font-semibold text-secondary-800 dark:text-secondary-100 leading-tight break-words">
{tech.name}
</span>
<span class="text-[11px] sm:text-xs uppercase tracking-wider text-secondary-500 dark:text-secondary-400 leading-tight">
{tech.context}
</span>
</li>
))}
</ul>
<p class="text-center text-sm text-secondary-500 dark:text-secondary-400 mt-10 sm:mt-12">
Also working with <span class="font-semibold text-secondary-700 dark:text-secondary-200">React Native</span>, <span class="font-semibold text-secondary-700 dark:text-secondary-200">MySQL</span>, <span class="font-semibold text-secondary-700 dark:text-secondary-200">Docker</span>, and <span class="font-semibold text-secondary-700 dark:text-secondary-200">GitHub Actions</span> — <a href="/services" class="text-primary-600 dark:text-primary-400 font-semibold hover:underline">see the full stack &rarr;</a>
</p>
</div>
</section>
<!-- Final CTA Section - Enhanced -->
<section class="py-24 bg-gradient-to-br from-secondary-900 via-primary-900 to-primary-800 relative overflow-hidden">
<!-- Background decorations -->
+122
View File
@@ -0,0 +1,122 @@
import { test, expect } from '@playwright/test';
const WA_HREF =
'https://wa.me/919561417403?text=Hi%20WorkRoot%2C%20I%27d%20like%20to%20discuss%20a%20project.';
/**
* Verifies the global WhatsApp FAB and the Mobile Sticky CTA bar render
* correctly across viewports, expose working contact links, and never
* cause horizontal overflow on a 360px viewport.
*/
test.describe('WhatsApp FAB (global)', () => {
test('renders on every page at desktop width', async ({ page }) => {
await page.setViewportSize({ width: 1280, height: 800 });
for (const path of ['/', '/about', '/services', '/contact']) {
await page.goto(path);
const fab = page.getByTestId('whatsapp-fab');
await expect(fab, `FAB missing on ${path}`).toBeVisible();
await expect(fab).toHaveAttribute('href', WA_HREF);
await expect(fab).toHaveAttribute('target', '_blank');
await expect(fab).toHaveAttribute('rel', /noopener/);
await expect(fab).toHaveAttribute('aria-label', 'Chat on WhatsApp');
}
});
test('renders on home at 360px mobile width', async ({ page }) => {
await page.setViewportSize({ width: 360, height: 800 });
await page.goto('/');
const fab = page.getByTestId('whatsapp-fab');
await expect(fab).toBeVisible();
// FAB is 56x56
const box = await fab.boundingBox();
expect(box).not.toBeNull();
expect(box!.width).toBeGreaterThanOrEqual(48);
expect(box!.height).toBeGreaterThanOrEqual(48);
// FAB does not horizontally overflow the viewport
expect(box!.x + box!.width).toBeLessThanOrEqual(360);
});
});
test.describe('Mobile Sticky CTA Bar', () => {
test('visible at 360px and exposes 3 working actions', async ({ page }) => {
await page.setViewportSize({ width: 360, height: 800 });
await page.goto('/');
const bar = page.getByTestId('mobile-sticky-cta-bar');
await expect(bar).toBeVisible();
const callBtn = page.getByTestId('mobile-cta-call');
const waBtn = page.getByTestId('mobile-cta-whatsapp');
const quoteBtn = page.getByTestId('mobile-cta-quote');
await expect(callBtn).toBeVisible();
await expect(waBtn).toBeVisible();
await expect(quoteBtn).toBeVisible();
await expect(callBtn).toHaveAttribute('href', 'tel:+919561417403');
await expect(waBtn).toHaveAttribute('href', WA_HREF);
await expect(quoteBtn).toHaveAttribute('href', '/contact');
// Each action is at least a 48px tall touch target
for (const btn of [callBtn, waBtn, quoteBtn]) {
const box = await btn.boundingBox();
expect(box).not.toBeNull();
expect(box!.height).toBeGreaterThanOrEqual(48);
}
// The bar spans the full viewport width and is pinned to the bottom
const barBox = await bar.boundingBox();
expect(barBox).not.toBeNull();
expect(barBox!.x).toBeLessThanOrEqual(0.5);
expect(barBox!.width).toBeGreaterThanOrEqual(355);
expect(barBox!.width).toBeLessThanOrEqual(360);
// No horizontal overflow at 360px (match existing site tolerance: viewport + 20px)
const bodyWidth = await page.evaluate(() => document.body.scrollWidth);
expect(bodyWidth).toBeLessThanOrEqual(360 + 20);
// The page must not be horizontally scrolled — confirms fixed elements fit.
const scrollX = await page.evaluate(() => window.scrollX);
expect(scrollX).toBe(0);
});
test('hidden at >=768px (desktop / tablet landscape)', async ({ page }) => {
await page.setViewportSize({ width: 1280, height: 800 });
await page.goto('/');
const bar = page.getByTestId('mobile-sticky-cta-bar');
// Either not visible, or has md:hidden applied (display:none via media query)
await expect(bar).toBeHidden();
});
test('renders on every key page on mobile', async ({ page }) => {
await page.setViewportSize({ width: 360, height: 800 });
for (const path of ['/', '/about', '/services', '/portfolio', '/blog', '/contact']) {
await page.goto(path);
await expect(
page.getByTestId('mobile-sticky-cta-bar'),
`Sticky CTA bar missing on ${path}`,
).toBeVisible();
}
});
test('FAB sits above the sticky bar on mobile (no overlap)', async ({ page }) => {
await page.setViewportSize({ width: 360, height: 800 });
await page.goto('/');
const fab = page.getByTestId('whatsapp-fab');
const bar = page.getByTestId('mobile-sticky-cta-bar');
const fabBox = await fab.boundingBox();
const barBox = await bar.boundingBox();
expect(fabBox).not.toBeNull();
expect(barBox).not.toBeNull();
// The bottom of the FAB sits above the top of the sticky bar.
expect(fabBox!.y + fabBox!.height).toBeLessThanOrEqual(barBox!.y);
});
});