Compare commits

...
21 Commits
Author SHA1 Message Date
platform-mcpandClaude Opus 4.8 fccc0115c4 feat: render WRNexus bot widget globally via shared layout
Deploy to Production / Build & Verify (push) Failing after 16m44s
E2E Test Suite / Security Header Tests (push) Failing after 10m27s
E2E Test Suite / Mobile Device Tests (push) Failing after 10m28s
E2E Test Suite / Cross-Browser Regression (webkit) (push) Failing after 11m21s
E2E Test Suite / Cross-Browser Regression (firefox) (push) Failing after 12m13s
E2E Test Suite / Cross-Browser Regression (chromium) (push) Failing after 13m6s
E2E Test Suite / Destructive & Chaos Tests (push) Failing after 13m58s
E2E Test Suite / Form Interaction Tests (push) Failing after 14m51s
E2E Test Suite / Smoke Tests (P0) (push) Failing after 21m43s
E2E Test Suite / Critical User Journeys (push) Has been skipped
E2E Test Suite / API Integration Tests (push) Has been skipped
Deploy to Production / Pre-Deploy Tests (push) Failing after 14m59s
E2E Test Suite / Test Report Summary (push) Failing after 14m58s
Deploy to Production / Deploy to Fly.io (push) Failing after 13m21s
Deploy to Production / Deploy to VPS (PM2) (push) Failing after 13m48s
Deploy to Production / Deploy to Render (push) Failing after 14m14s
Deploy to Production / Deploy to Railway (push) Failing after 14m40s
Deploy to Production / Post-Deploy Verification (push) Failing after 14m10s
Deploy to Production / Notify on Failure (push) Successful in 1s
Inject the WRNexus bot widget loader (bots/widget.js) once into
BaseLayout.astro's <head> so the chat widget appears on every route
(pages + blog posts) without per-page duplication. Uses is:inline so
Astro does not bundle/transform the external script, preserving the
data-bot-id and async attributes.

Update CSP in middleware.ts to permit the widget: script-src and
connect-src already allowed https://api.wrnexus.com (web-push SDK);
added it to frame-src for the widget's iframe. No other directives
weakened.

Verified: build is clean, widget tag served on /, /about, /contact,
and a blog post; widget.js loads 200; zero console CSP violations.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 00:29:32 +05:30
platform-mcp 189260bbb3 pre-agent: Add WRNexus chat widget script to layout 2026-06-26 00:27:14 +05:30
platform-mcpandClaude Opus 4.8 8e7070cbaf feat(footer): make push notification button reflect subscription state
E2E Test Suite / Smoke Tests (P0) (push) Failing after 9m36s
E2E Test Suite / Critical User Journeys (push) Has been skipped
E2E Test Suite / API Integration Tests (push) Has been skipped
E2E Test Suite / Destructive & Chaos Tests (push) Failing after 10m42s
E2E Test Suite / Form Interaction Tests (push) Failing after 11m35s
E2E Test Suite / Security Header Tests (push) Failing after 11m20s
E2E Test Suite / Mobile Device Tests (push) Failing after 12m13s
E2E Test Suite / Cross-Browser Regression (webkit) (push) Failing after 13m5s
E2E Test Suite / Cross-Browser Regression (firefox) (push) Failing after 13m57s
E2E Test Suite / Cross-Browser Regression (chromium) (push) Failing after 14m50s
E2E Test Suite / Test Report Summary (push) Failing after 12m50s
Deploy to Production / Build & Verify (push) Failing after 19m19s
Deploy to Production / Pre-Deploy Tests (push) Failing after 14m59s
Deploy to Production / Deploy to Fly.io (push) Failing after 12m23s
Deploy to Production / Deploy to VPS (PM2) (push) Failing after 12m26s
Deploy to Production / Deploy to Render (push) Failing after 13m16s
Deploy to Production / Deploy to Railway (push) Failing after 14m8s
Deploy to Production / Post-Deploy Verification (push) Failing after 14m57s
Deploy to Production / Notify on Failure (push) Failing after 14m58s
The WRNexus web-push button always showed "Enable notifications". It now
reads WRNexusPush.isSubscribed() on load and renders the correct state:
- hides entirely when SDK/browser push support is missing
- "Enable notifications" -> calls prompt() (user gesture)
- "Unsubscribe" when already subscribed -> calls unsubscribe()
- disabled "Notifications blocked" when permission is denied
- in-progress disabled state to prevent double-clicks; re-reads state after
  each action and re-renders the label

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-24 02:39:37 +05:30
platform-mcp a8d7db562d pre-agent: Make push notification button reflect subscription 2026-06-24 02:37:20 +05:30
platform-mcp e220f46d35 agent: Host WRNexus push service worker at site root 2026-06-24 01:45:37 +05:30
platform-mcp 93460ece19 pre-agent: Host WRNexus push service worker at site root 2026-06-24 01:42:48 +05:30
platform-mcpandClaude Opus 4.8 7b67897612 fix(csp): allow WRNexus push SDK origin in script-src and connect-src
E2E Test Suite / Form Interaction Tests (push) Failing after 10m29s
E2E Test Suite / Security Header Tests (push) Failing after 10m15s
E2E Test Suite / Mobile Device Tests (push) Failing after 11m8s
E2E Test Suite / Cross-Browser Regression (webkit) (push) Failing after 12m0s
E2E Test Suite / Cross-Browser Regression (firefox) (push) Failing after 12m52s
E2E Test Suite / Cross-Browser Regression (chromium) (push) Failing after 13m44s
E2E Test Suite / Destructive & Chaos Tests (push) Failing after 14m37s
E2E Test Suite / Smoke Tests (P0) (push) Failing after 21m17s
E2E Test Suite / Critical User Journeys (push) Has been skipped
E2E Test Suite / API Integration Tests (push) Has been skipped
E2E Test Suite / Test Report Summary (push) Failing after 12m14s
Deploy to Production / Notify on Failure (push) Successful in 1s
Deploy to Production / Build & Verify (push) Failing after 5m47s
Deploy to Production / Pre-Deploy Tests (push) Failing after 14m54s
Deploy to Production / Deploy to Railway (push) Failing after 14m58s
Deploy to Production / Deploy to Render (push) Failing after 14m35s
Deploy to Production / Deploy to VPS (PM2) (push) Failing after 14m6s
Deploy to Production / Deploy to Fly.io (push) Failing after 13m43s
Deploy to Production / Post-Deploy Verification (push) Failing after 14m11s
The web-push SDK (https://api.wrnexus.com/push/sdk.js) and its runtime
config fetch were blocked by the Content-Security-Policy set in
src/middleware.ts. Added https://api.wrnexus.com to script-src (prod and
dev) and connect-src. No wildcards introduced; existing analytics
origins (GTM, GA, Plausible) unchanged.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-24 01:38:51 +05:30
platform-mcp c5f79f1fd2 pre-agent: Allow WRNexus push SDK in CSP script-src 2026-06-24 01:35:55 +05:30
platform-mcpandClaude Opus 4.8 41abcaa7a4 feat: integrate WRNexus web-push SDK and subscribe control
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
Deploy to Production / Pre-Deploy Tests (push) Failing after 12m56s
Deploy to Production / Build & Verify (push) Failing after 17m52s
Deploy to Production / Deploy to Fly.io (push) Failing after 12m36s
Deploy to Production / Deploy to VPS (PM2) (push) Failing after 13m3s
Deploy to Production / Deploy to Render (push) Failing after 13m30s
Deploy to Production / Deploy to Railway (push) Failing after 13m55s
Deploy to Production / Post-Deploy Verification (push) Failing after 14m57s
Deploy to Production / Notify on Failure (push) Successful in 1s
- Add WRNexus push SDK (is:inline) to BaseLayout <head> so it loads on
  every page that should collect subscribers (api.wrnexus.com/push/sdk.js)
- Add "Enable notifications" subscribe button in the footer wired via
  addEventListener to window.WRNexusPush.subscribe()

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-24 01:33:20 +05:30
platform-mcp 966f3b23fa pre-agent: Add WRNexus push notification SDK and subscribe bu 2026-06-24 01:30:45 +05:30
platform-mcp 45b30918a0 pre-agent: Add WRNexus push notification SDK and subscribe bu 2026-06-24 01:30:12 +05:30
platform-mcp 7eeec7e781 pre-agent: Draft PR and earned-mentions plan 2026-06-19 09:38:43 +05:30
platform-mcp 527131a462 pre-agent: Create directory listing and Google Business Profi 2026-06-19 09:36:05 +05:30
platform-mcpandClaude Opus 4.8 6b07b83583 chore(seo): open robots.txt to all major AI crawlers for GEO
E2E Test Suite / Cross-Browser Regression (webkit) (push) Failing after 10m48s
E2E Test Suite / Cross-Browser Regression (firefox) (push) Failing after 11m43s
E2E Test Suite / Cross-Browser Regression (chromium) (push) Failing after 12m35s
E2E Test Suite / Destructive & Chaos Tests (push) Failing after 13m27s
E2E Test Suite / Form Interaction Tests (push) Failing after 14m20s
E2E Test Suite / Security Header Tests (push) Failing after 14m1s
E2E Test Suite / Mobile Device Tests (push) Failing after 14m56s
E2E Test Suite / Smoke Tests (P0) (push) Failing after 21m57s
E2E Test Suite / Critical User Journeys (push) Has been skipped
E2E Test Suite / API Integration Tests (push) Has been skipped
Deploy to Production / Build & Verify (push) Failing after 17m16s
E2E Test Suite / Test Report Summary (push) Failing after 13m13s
Deploy to Production / Pre-Deploy Tests (push) Failing after 12m22s
Deploy to Production / Deploy to Fly.io (push) Failing after 13m13s
Deploy to Production / Deploy to VPS (PM2) (push) Failing after 14m5s
Deploy to Production / Deploy to Render (push) Failing after 14m5s
Deploy to Production / Deploy to Railway (push) Failing after 14m57s
Deploy to Production / Post-Deploy Verification (push) Failing after 13h5m7s
Deploy to Production / Notify on Failure (push) Failing after 15m4s
Repo robots.txt already matched the deployed file (verified via diff
against https://workroot.in/robots.txt). Added commonly-cited AI agents
that were still missing, each with Allow: /:
- OpenAI: OAI-SearchBot
- Anthropic: ClaudeBot, Claude-SearchBot, Claude-User
- Perplexity: Perplexity-User
- Meta: Meta-ExternalAgent / meta-externalagent
- Apple: Applebot, Applebot-Extended
- Amazon: Amazonbot
- ByteDance: Bytespider
- Cohere: cohere-ai
- DuckDuckGo: DuckAssistBot
- You.com: YouBot
- Diffbot

Disallow list unchanged (/admin/, /private/, /_astro/) so no content
directory needed for citation is blocked. Sitemap line preserved.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-19 09:35:47 +05:30
platform-mcp c7e130ce4d pre-agent: Verify robots.txt keeps AI crawlers open 2026-06-19 09:34:50 +05:30
platform-mcpandClaude Opus 4.8 5a2b68763f chore(content): unify company facts for NAP consistency
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 / Smoke Tests (P0) (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
Ping Search Engines / Notify Search Engines (push) Failing after 14m45s
Reconcile inconsistent company facts to the canonical set (single
source of truth):

- Structured-data Organization/LocalBusiness/publisher/provider names
  now use the legal entity name "WorkRoot IT Solutions LLP"
  (SEO.astro, utils/seo.ts) for NAP consistency. Brand short forms
  ("WorkRoot", "WorkRoot IT Solutions") retained only as display brand
  and alternateName.
- Headline government-reach proof figure unified to "11+ districts"
  across all company-level proof bands and marketing/SEO copy
  (ProofNumbers, /about, /portfolio, homepage, /services, SEO desc).
  Specific Bihar Police Management System case studies / service-page
  stat tiles keep the factual "11 districts" — documented rationale.
- Verified phone (+91 9561417403), email (admin@workroot.in),
  founded (2022), 20+ projects, 50,000+ records, 99.9% uptime and
  IIT Patna partnership are consistent site-wide.

Added .agent/scratch/FACTS-CANONICAL.md as the single source of truth
plus an off-site checklist (LinkedIn, Clutch, GoodFirms, Crunchbase,
GeM) to keep external listings identical. Build passes.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-19 09:34:30 +05:30
platform-mcp 84624976a8 pre-agent: Audit and unify company facts (NAP consistency) 2026-06-19 09:23:55 +05:30
platform-mcpandClaude Opus 4.8 a3dd4b9ffa content: add quotable, extractable company facts + comparison table to homepage
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 11m34s
Deploy to Production / Pre-Deploy Tests (push) Failing after 13m8s
Deploy to Production / Build & Verify (push) Failing after 16m0s
Deploy to Production / Deploy to Fly.io (push) Failing after 14m5s
Deploy to Production / Deploy to VPS (PM2) (push) Failing after 14m6s
Deploy to Production / Deploy to Render (push) Failing after 14m57s
Deploy to Production / Deploy to Railway (push) Failing after 14m58s
Deploy to Production / Post-Deploy Verification (push) Failing after 14m57s
Deploy to Production / Notify on Failure (push) Failing after 13h10m32s
Add an LLM-citable "WorkRoot at a glance" section to the homepage with 7
standalone factual sentences that each bundle the company name + a concrete
claim + a number (founded 2022, 20+ projects, 11 districts, 50,000+ records,
99.9% uptime, IIT Patna partnership, GeM empanelment, 15,000+ officers, 8+
years), so each line stays accurate when quoted out of context.

- New companyFacts list + "Custom software vs off-the-shelf ERP" comparison
  table in src/pages/index.astro (reuses the table pattern from the custom-ERP
  service page; final row carries the canonical 11-districts / 99.9% proof).
- Rewrite vague HomepageFaq answers into definitive, self-contained one-
  paragraph answers; visible text still drives the single FAQPage JSON-LD.
- Normalize stray "11+ districts" in the services array to the canonical
  "11 districts" for exact sitewide numerical consistency.

Verified: build passes; rendered homepage shows all 7 facts, the comparison
table, definitive FAQ answers, exactly one FAQPage JSON-LD, and no "11+".

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-19 09:23:30 +05:30
platform-mcp 0121641163 pre-agent: Add quotable, extractable facts for LLM citation 2026-06-19 09:17:58 +05:30
platform-mcpandClaude Opus 4.8 8d35785a4c content: add buyer-question blog posts on custom ERP cost & GeM empanelment
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 / Smoke Tests (P0) (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
Ping Search Engines / Notify Search Engines (push) Failing after 12m33s
Add two long-form (1,200+ word) blog posts targeting buyer search
questions, with valid content-collection frontmatter, comparison
tables, India-specific rupee figures, and definitive FAQ blocks:

- how-much-custom-erp-cost-india-2026.md (hero: erp-systems.jpg)
- gem-empanelment-process-software-vendors.md (hero: govt-systems.jpg)

Both use category "Web Development" (valid enum), author "Ajay", and
reuse existing hero images. Verified: astro build passes schema
validation and both render at /blog/<slug> with working hero images
and appear in the blog index.

Also record the 1-2 posts/month editorial cadence in
.agent/scratch/SEO-NOTES.md.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-19 09:17:34 +05:30
platform-mcp c1585570c6 pre-agent: Expand blog into a buyer-question content engine 2026-06-19 09:12:13 +05:30
27 changed files with 1497 additions and 368 deletions
+48 -38
View File
@@ -1,11 +1,11 @@
# Graph Report - 2e709500-79ab-403f-9ca0-3f027ac9f36e (2026-06-19)
# Graph Report - 2e709500-79ab-403f-9ca0-3f027ac9f36e (2026-06-24)
## Corpus Check
- 61 files · ~611,807 words
- 64 files · ~615,668 words
- Verdict: corpus is large enough that graph structure adds value.
## Summary
- 596 nodes · 620 edges · 114 communities (87 shown, 27 thin omitted)
- 619 nodes · 640 edges · 117 communities (90 shown, 27 thin omitted)
- Extraction: 99% EXTRACTED · 1% INFERRED · 0% AMBIGUOUS · INFERRED: 4 edges (avg confidence: 0.8)
- Token cost: 0 input · 0 output
@@ -50,9 +50,8 @@
- [[_COMMUNITY_Community 34|Community 34]]
- [[_COMMUNITY_Community 35|Community 35]]
- [[_COMMUNITY_Community 36|Community 36]]
- [[_COMMUNITY_Community 41|Community 41]]
- [[_COMMUNITY_Community 42|Community 42]]
- [[_COMMUNITY_Community 43|Community 43]]
- [[_COMMUNITY_Community 37|Community 37]]
- [[_COMMUNITY_Community 38|Community 38]]
- [[_COMMUNITY_Community 44|Community 44]]
- [[_COMMUNITY_Community 45|Community 45]]
- [[_COMMUNITY_Community 46|Community 46]]
@@ -71,6 +70,9 @@
- [[_COMMUNITY_Community 59|Community 59]]
- [[_COMMUNITY_Community 60|Community 60]]
- [[_COMMUNITY_Community 61|Community 61]]
- [[_COMMUNITY_Community 62|Community 62]]
- [[_COMMUNITY_Community 63|Community 63]]
- [[_COMMUNITY_Community 64|Community 64]]
## God Nodes (most connected - your core abstractions)
1. `Performance Testing Scripts` - 12 edges
@@ -94,7 +96,7 @@
- `POST()` --calls--> `logApiRequest()` [INFERRED]
src/pages/api/newsletter.ts → src/utils/logger.ts
## Communities (114 total, 27 thin omitted)
## Communities (117 total, 27 thin omitted)
### Community 0 - "Community 0"
Cohesion: 0.05
@@ -144,49 +146,49 @@ Nodes (11): Code Example, code:astro (---), code:javascript (// Traditional SPA:
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 18 - "Community 18"
Cohesion: 0.43
Nodes (6): trackCaseStudyView(), trackEvent(), trackExternalLink(), trackFormSubmit(), trackNewsletterSignup(), trackPortfolioFilter()
### Community 16 - "Community 16"
Cohesion: 0.2
Nodes (9): Catalogue Listing vs Bidding: How You Actually Win, Common Reasons Software Vendors Get Rejected or Delayed, Document Checklist, Fees, Caution Money, and Timelines, Frequently Asked Questions, MSME and Startup Benefits Worth Claiming, Step-by-Step Registration Process, Turning GeM Access Into Government Contracts (+1 more)
### Community 19 - "Community 19"
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 18 - "Community 18"
Cohesion: 0.22
Nodes (8): 2026 Cost Ranges by Project Tier, A Simple Way to Estimate Your Own Cost, Build vs Buy: When Custom Actually Pays Off, Frequently Asked Questions, Getting an Accurate Number for Your Project, Hidden Costs Buyers Forget to Budget, The Biggest Cost Driver: The Team, What "Custom ERP" Actually Means for Pricing
### Community 20 - "Community 20"
Cohesion: 0.46
Nodes (7): captureScreenshot(), ensureDir(), generateIndex(), main(), screenshotPath(), setTheme(), waitForPageReady()
Cohesion: 0.43
Nodes (6): trackCaseStudyView(), trackEvent(), trackExternalLink(), trackFormSubmit(), trackNewsletterSignup(), trackPortfolioFilter()
### Community 21 - "Community 21"
Cohesion: 0.36
Nodes (4): deleteFromDB(), getAllFromDB(), openDB(), processSyncQueue()
### Community 22 - "Community 22"
Cohesion: 0.43
Nodes (6): trackCaseStudyView(), trackEvent(), trackExternalLink(), trackFormSubmit(), trackNewsletterSignup(), trackPortfolioFilter()
### Community 23 - "Community 23"
Cohesion: 0.46
Nodes (7): checkRateLimit(), corsHeaders(), escapeHtml(), OPTIONS(), POST(), sendContactEmail(), validateContactForm()
### Community 24 - "Community 24"
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 25 - "Community 25"
### Community 22 - "Community 22"
Cohesion: 0.46
Nodes (7): captureScreenshot(), ensureDir(), generateIndex(), main(), screenshotPath(), setTheme(), waitForPageReady()
### Community 26 - "Community 26"
### Community 23 - "Community 23"
Cohesion: 0.36
Nodes (4): deleteFromDB(), getAllFromDB(), openDB(), processSyncQueue()
### Community 24 - "Community 24"
Cohesion: 0.46
Nodes (7): checkRateLimit(), corsHeaders(), escapeHtml(), OPTIONS(), POST(), sendContactEmail(), validateContactForm()
### Community 25 - "Community 25"
Cohesion: 0.43
Nodes (6): trackCaseStudyView(), trackEvent(), trackExternalLink(), trackFormSubmit(), trackNewsletterSignup(), trackPortfolioFilter()
### Community 26 - "Community 26"
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 27 - "Community 27"
Cohesion: 0.48
Nodes (5): initAllAnimations(), initButtonRipple(), initCounters(), initProgressBars(), initScrollReveal()
Cohesion: 0.46
Nodes (7): captureScreenshot(), ensureDir(), generateIndex(), main(), screenshotPath(), setTheme(), waitForPageReady()
### Community 28 - "Community 28"
Cohesion: 0.48
Nodes (5): extractJsonLd(), main(), validateOpenGraph(), validatePage(), validateTwitterCard()
Cohesion: 0.36
Nodes (4): deleteFromDB(), getAllFromDB(), openDB(), processSyncQueue()
### Community 29 - "Community 29"
Cohesion: 0.48
@@ -197,23 +199,31 @@ Cohesion: 0.48
Nodes (5): extractJsonLd(), main(), validateOpenGraph(), validatePage(), validateTwitterCard()
### Community 31 - "Community 31"
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
Cohesion: 0.48
Nodes (5): initAllAnimations(), initButtonRipple(), initCounters(), initProgressBars(), initScrollReveal()
### Community 32 - "Community 32"
Cohesion: 0.48
Nodes (5): extractJsonLd(), main(), validateOpenGraph(), validatePage(), validateTwitterCard()
### Community 33 - "Community 33"
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 35 - "Community 35"
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 37 - "Community 37"
Cohesion: 0.6
Nodes (3): formatUptime(), GET(), percentile()
### Community 36 - "Community 36"
### Community 38 - "Community 38"
Cohesion: 0.6
Nodes (3): formatUptime(), GET(), percentile()
## Knowledge Gaps
- **140 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` (+135 more)
- **156 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` (+151 more)
These have ≤1 connection - possible missing edges or undocumented components.
- **27 thin communities (<3 nodes) omitted from report** — run `graphify query` to explore isolated nodes.
@@ -223,7 +233,7 @@ _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?**
_140 weakly-connected nodes found - possible documentation gaps or missing edges._
_156 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?**
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_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"}]}
@@ -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": "L43"}, {"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": "L52"}, {"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": "L69"}, {"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": "L69"}]}
File diff suppressed because one or more lines are too long
+697 -290
View File
File diff suppressed because it is too large Load Diff
+52
View File
@@ -58,6 +58,9 @@ Allow: /
User-agent: GPTBot
Allow: /
User-agent: OAI-SearchBot
Allow: /
# Anthropic/Claude
User-agent: anthropic-ai
Allow: /
@@ -65,10 +68,22 @@ Allow: /
User-agent: Claude-Web
Allow: /
User-agent: ClaudeBot
Allow: /
User-agent: Claude-SearchBot
Allow: /
User-agent: Claude-User
Allow: /
# Perplexity AI
User-agent: PerplexityBot
Allow: /
User-agent: Perplexity-User
Allow: /
# Google Gemini/Bard
User-agent: Google-Extended
Allow: /
@@ -80,3 +95,40 @@ Allow: /
# Meta AI
User-agent: FacebookBot
Allow: /
User-agent: Meta-ExternalAgent
Allow: /
User-agent: meta-externalagent
Allow: /
# Apple Intelligence
User-agent: Applebot
Allow: /
User-agent: Applebot-Extended
Allow: /
# Amazon AI
User-agent: Amazonbot
Allow: /
# ByteDance / TikTok AI
User-agent: Bytespider
Allow: /
# Cohere AI
User-agent: cohere-ai
Allow: /
# DuckDuckGo AI (DuckAssist)
User-agent: DuckAssistBot
Allow: /
# You.com AI
User-agent: YouBot
Allow: /
# Diffbot (knowledge graph / AI training)
User-agent: Diffbot
Allow: /
+166
View File
@@ -0,0 +1,166 @@
/*!
* WRNexus Web Push — service worker.
*
* ┌─────────────────────────────────────────────────────────────────────────┐
* │ HOST THIS FILE ON YOUR OWN ORIGIN. │
* │ │
* │ A service worker can only control the origin it is served from, so the │
* │ browser will NOT let wrnexus.com register a worker for your site. Copy │
* │ this file to the ROOT of your site and serve it at: │
* │ │
* │ https://your-domain.example/wrnexus-push-sw.js │
* │ │
* │ It must be served same-origin, over HTTPS, with │
* │ `Content-Type: text/javascript`. To control the whole site from a │
* │ non-root path, also send `Service-Worker-Allowed: /`. │
* └─────────────────────────────────────────────────────────────────────────┘
*
* Core behaviour needs NO configuration — the click/delivery tracking URLs are
* carried inside each push payload built by the WRNexus API. The optional
* CONFIG block below is only used to silently re-subscribe after the browser
* rotates a subscription (`pushsubscriptionchange`); leave it blank to skip
* that.
*/
/* eslint-disable no-restricted-globals */
// ── OPTIONAL: only needed for automatic re-subscription on key rotation ──────
var WRNEXUS_PUSH_CONFIG = {
// Your site's publicKey (the same value as data-site-id in the snippet).
siteKey: '',
// The WRNexus API origin (e.g. 'https://api.wrnexus.com').
apiBase: '',
// Your site's VAPID public key (shown in Push → Settings).
vapidPublicKey: '',
};
self.addEventListener('install', function () {
self.skipWaiting();
});
self.addEventListener('activate', function (event) {
event.waitUntil(self.clients.claim());
});
// CORS-simple POST (text/plain, no custom headers, no credentials) so the
// receipt reaches the WRNexus API cross-origin without a preflight.
function postReceipt(url) {
if (!url) return Promise.resolve();
return fetch(url, {
method: 'POST',
headers: { 'Content-Type': 'text/plain;charset=UTF-8' },
credentials: 'omit',
keepalive: true,
mode: 'cors',
}).catch(function () {});
}
function urlBase64ToUint8Array(base64String) {
var padding = '='.repeat((4 - (base64String.length % 4)) % 4);
var base64 = (base64String + padding).replace(/-/g, '+').replace(/_/g, '/');
var raw = self.atob(base64);
var out = new Uint8Array(raw.length);
for (var i = 0; i < raw.length; ++i) out[i] = raw.charCodeAt(i);
return out;
}
function keyToB64(buf) {
if (!buf) return '';
var bytes = new Uint8Array(buf);
var binary = '';
for (var i = 0; i < bytes.length; i++) binary += String.fromCharCode(bytes[i]);
return self
.btoa(binary)
.replace(/\+/g, '-')
.replace(/\//g, '_')
.replace(/=+$/, '');
}
// ── Incoming notification ─────────────────────────────────────────────────
self.addEventListener('push', function (event) {
var data = {};
try {
data = event.data ? event.data.json() : {};
} catch (_e) {
try {
data = { title: event.data && event.data.text() };
} catch (_e2) {
data = {};
}
}
var title = data.title || 'Notification';
var options = {
body: data.body || '',
icon: data.icon || undefined,
image: data.image || undefined,
badge: data.badge || undefined,
tag: data.tag || data.campaignId || undefined,
requireInteraction: !!data.requireInteraction,
data: {
clickUrl: data.clickUrl || data.url || '/',
url: data.url || '/',
openUrl: data.openUrl || '',
campaignId: data.campaignId || '',
subscriptionId: data.subscriptionId || '',
},
};
event.waitUntil(
Promise.all([
self.registration.showNotification(title, options),
// Delivery receipt — records a `delivered` event for analytics.
postReceipt(data.deliveredUrl),
])
);
});
// ── Click → record + open the destination ─────────────────────────────────
self.addEventListener('notificationclick', function (event) {
var d = (event.notification && event.notification.data) || {};
event.notification.close();
// `clickUrl` is a signed WRNexus redirect: hitting it records the `clicked`
// event and 302s to the real destination, so tracking fires from the open.
var target = d.clickUrl || d.url || '/';
event.waitUntil(
self.clients.matchAll({ type: 'window', includeUncontrolled: true }).then(function (list) {
// Reuse an existing tab on the destination when possible.
for (var i = 0; i < list.length; i++) {
var client = list[i];
if (d.url && client.url === d.url && 'focus' in client) return client.focus();
}
if (self.clients.openWindow) return self.clients.openWindow(target);
return undefined;
})
);
});
// ── Optional: re-subscribe after the browser rotates the subscription ──────
self.addEventListener('pushsubscriptionchange', function (event) {
var cfg = WRNEXUS_PUSH_CONFIG || {};
if (!cfg.siteKey || !cfg.apiBase || !cfg.vapidPublicKey) return;
event.waitUntil(
self.registration.pushManager
.subscribe({ userVisibleOnly: true, applicationServerKey: urlBase64ToUint8Array(cfg.vapidPublicKey) })
.then(function (sub) {
return fetch(cfg.apiBase.replace(/\/+$/, '') + '/api/push/subscriptions', {
method: 'POST',
headers: { 'Content-Type': 'text/plain;charset=UTF-8' },
credentials: 'omit',
mode: 'cors',
body: JSON.stringify({
key: cfg.siteKey,
endpoint: sub.endpoint,
keys: {
p256dh: keyToB64(sub.getKey('p256dh')),
auth: keyToB64(sub.getKey('auth')),
},
metadata: { source: 'pushsubscriptionchange' },
}),
}).catch(function () {});
})
.catch(function () {})
);
});
+117
View File
@@ -166,6 +166,23 @@ const currentYear = new Date().getFullYear();
By subscribing, you agree to our
<a href="/privacy" class="text-primary hover:underline">Privacy Policy</a>.
</p>
<!-- Web push opt-in (WRNexus). User-gesture subscribe control.
Hidden by default; the client script below reveals it only when the
WRNexus SDK + browser push support are available, and sets the
correct label based on the visitor's current subscription state. -->
<button
id="wrnexus-subscribe"
type="button"
hidden
aria-label="Enable notifications"
class="mt-6 w-full px-4 py-3 bg-secondary-800 border border-secondary-700 text-white font-medium rounded-lg hover:bg-secondary-700 focus:outline-none focus:ring-2 focus:ring-primary focus:ring-offset-2 focus:ring-offset-secondary-900 transition-all duration-200 text-sm flex items-center justify-center gap-2 disabled:opacity-60 disabled:cursor-not-allowed"
>
<svg class="w-4 h-4 flex-shrink-0" fill="none" viewBox="0 0 24 24" stroke="currentColor" aria-hidden="true">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 17h5l-1.405-1.405A2.032 2.032 0 0118 14.158V11a6.002 6.002 0 00-4-5.659V5a2 2 0 10-4 0v.341C7.67 6.165 6 8.388 6 11v3.159c0 .538-.214 1.055-.595 1.436L4 17h5m6 0v1a3 3 0 11-6 0v-1m6 0H9" />
</svg>
<span id="wrnexus-subscribe-label">Enable notifications</span>
</button>
</div>
</div>
</div>
@@ -193,6 +210,106 @@ const currentYear = new Date().getFullYear();
</div>
</footer>
<!-- WRNexus push subscribe: state-aware control wired via addEventListener
(Astro strips inline on* handlers). The button reflects whether the
visitor is already subscribed and lets them toggle the subscription. -->
<script is:inline>
(function () {
function init() {
var btn = document.getElementById('wrnexus-subscribe');
var label = document.getElementById('wrnexus-subscribe-label');
if (!btn || !label) return;
var push = window.WRNexusPush;
var supported =
push &&
'Notification' in window &&
'serviceWorker' in navigator &&
'PushManager' in window;
// No SDK or no browser push support: keep the button hidden entirely.
if (!supported) {
btn.hidden = true;
return;
}
// Permission permanently blocked by the user: the SDK can't re-prompt,
// so surface a clear, non-actionable disabled state instead of a button
// that silently does nothing.
if (window.Notification && Notification.permission === 'denied') {
label.textContent = 'Notifications blocked';
btn.setAttribute('aria-label', 'Notifications are blocked in your browser settings');
btn.disabled = true;
btn.hidden = false;
return;
}
// Render the button for a given subscription state.
// Default UX: subscribed users get an actionable "Unsubscribe" toggle
// rather than a dead "Already subscribed" label.
// (Alternative: render a disabled "Already subscribed" indicator instead
// of the Unsubscribe toggle if product prefers a non-actionable state.)
function render(subscribed) {
if (subscribed) {
label.textContent = 'Unsubscribe';
btn.setAttribute('aria-label', 'Unsubscribe from notifications');
} else {
label.textContent = 'Enable notifications';
btn.setAttribute('aria-label', 'Enable notifications');
}
btn.dataset.subscribed = subscribed ? 'true' : 'false';
btn.disabled = false;
btn.hidden = false;
}
async function refresh() {
try {
var subscribed = await push.isSubscribed();
render(!!subscribed);
} catch (err) {
// On error, fall back to the unsubscribed label so the control stays usable.
console.error('[wrnexus-push] isSubscribed() failed:', err);
render(false);
}
}
btn.addEventListener('click', async function () {
if (btn.disabled) return;
var wasSubscribed = btn.dataset.subscribed === 'true';
var busyLabel = wasSubscribed ? 'Unsubscribing…' : 'Enabling…';
// In-progress state to prevent double-clicks while the promise is pending.
btn.disabled = true;
label.textContent = busyLabel;
try {
if (wasSubscribed) {
await push.unsubscribe();
} else {
// prompt() must run from this real user gesture.
await push.prompt();
}
} catch (err) {
console.error('[wrnexus-push] toggle failed:', err);
} finally {
// Re-read the authoritative state and re-render the label.
await refresh();
}
});
// Determine and render the initial state.
refresh();
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', init);
} else {
init();
}
})();
</script>
<script>
import { trackNewsletterSignup } from '../utils/analytics';
+18 -11
View File
@@ -18,61 +18,68 @@
// - Keyboard: native <button> handles Enter & Space; Tab moves between
// items. No custom key handling needed.
// Each answer is written as a definitive, self-contained paragraph: it can be
// read (or quoted) in isolation without the question for context, and the
// buyer-stage answers bundle the WorkRoot name with the canonical numbers
// (founded 2022 · 20+ projects · 11+ districts · 50,000+ records · 99.9%
// uptime · GeM · IIT Patna). Keep every figure identical to <ProofNumbers />
// and the homepage `companyFacts` set — the visible text below is also the
// source for the FAQPage JSON-LD emitted at the bottom of this component.
const moneyFaqs = [
{
question: 'How much does a project cost? Do you work with fixed budgets?',
answer:
'Projects start from ₹1,00,000, and most web, mobile, and ERP builds land between ₹2L and ₹15L depending on scope. And yes — we work with fixed budgets: tell us the number you have in mind and we will scope what we can deliver within it. Every quote is free, itemised, and no-obligation.',
'WorkRoot IT Solutions projects start from ₹1,00,000, and most web, mobile, and ERP builds land between ₹2,00,000 and ₹15,00,000 depending on scope. Yes — we work with fixed budgets: tell us the number you have in mind and we will scope exactly what we can deliver within it. Every quote is free, itemised, and carries no obligation.',
},
{
question: 'How much does a custom ERP cost?',
answer:
'Typical engagements run ₹2L–₹15L depending on scope. We offer fixed-price options for well-defined projects and time-and-materials for evolving ones.',
'A custom ERP from WorkRoot IT Solutions typically runs ₹2,00,000–₹15,00,000, with a genuinely production-grade single-department system starting around ₹6,00,000. We offer fixed-price quotes for well-defined scopes and time-and-materials pricing for evolving ones, and every estimate is free and itemised.',
},
{
question: 'What is your typical timeline?',
answer:
'416 weeks from kickoff to launch. MVPs ship in ~6 weeks on average.',
'Most WorkRoot IT Solutions engagements run 416 weeks from kickoff to launch, and a typical MVP ships in about 6 weeks. We work in 2-week sprints with a live staging environment from day one, so you see and steer working software early rather than waiting for a single big-bang release.',
},
{
question: 'Do you sign NDAs?',
answer:
'Yes — happily. We sign mutual NDAs before any detailed discussion.',
'Yes. WorkRoot IT Solutions signs a mutual NDA before any detailed discussion of your idea, data, or codebase — at no cost and with no conditions attached. Your concept and commercial details stay confidential from the very first call.',
},
{
question: 'Who owns the source code?',
answer:
'You do. Full IP transfer is included; we hand over repositories, deployment access, and documentation.',
'You do. Every WorkRoot IT Solutions engagement includes full intellectual-property transfer on delivery: you receive the complete source repositories, deployment access, and documentation, with no per-seat licence fees and no vendor lock-in. You are free to host, extend, or hand the system to another team at any time.',
},
{
question: 'Do you work with government tenders / GeM?',
answer:
"Yes. We're empanelled for relevant categories and have delivered platforms used across 11 districts in Bihar.",
'Yes. WorkRoot IT Solutions is empanelled on the Government e-Marketplace (GeM) for relevant IT service categories and can respond to government tenders. We have delivered public-service platforms live across 11+ districts of Bihar at 99.9% uptime, including a Police Management System and a Smart Crematorium Management System for the Bihar Government.',
},
{
question: 'What technologies do you work with?',
answer:
'Astro, Next.js, Angular, React Native, Spring Boot, Go, Node.js, PostgreSQL, and the full AWS/GCP stack.',
'WorkRoot IT Solutions builds on Astro, Next.js, Angular, and React Native on the front end; Spring Boot, Go, and Node.js on the back end; and PostgreSQL or MySQL for data — the same stack behind the IIT Patna University Management System and our multi-district government deployments, all deployed and operated on the AWS and GCP cloud.',
},
{
question: 'Do you offer ongoing support after launch?',
answer:
'Yes. We offer maintenance packages covering bug fixes, security patches, performance monitoring, and feature enhancements with response SLAs to match your uptime needs.',
'Yes. WorkRoot IT Solutions offers maintenance packages covering bug fixes, security patches, performance monitoring, and feature enhancements, each with a response SLA matched to your uptime needs — the same operational discipline that keeps our government systems running at 99.9% uptime.',
},
{
question: 'What is your development process?',
answer:
'Agile with 2-week sprints, weekly check-ins, and a live staging environment from day one. You see working software early and steer the roadmap as it ships.',
'WorkRoot IT Solutions works in Agile 2-week sprints with weekly check-ins and a live staging environment from day one. You see working software early and steer the roadmap as it ships, rather than waiting for a single final handover at the end of the project.',
},
{
question: 'Can you work with our existing team?',
answer:
'Absolutely. We augment in-house teams as readily as we deliver fully managed builds adapting to your repo, branch strategy, ticket system, and review process.',
'Absolutely. WorkRoot IT Solutions augments in-house teams as readily as it delivers fully managed builds, adapting to your repository, branch strategy, ticketing system, and code-review process so we slot into the way your team already works.',
},
{
question: 'How do you ensure project security?',
answer:
'Security is built into every phase: OWASP-aligned reviews, encrypted data at rest and in transit, role-based access control, regular dependency audits, and compliance with GDPR / SOC 2-style controls where the project demands them.',
'Security is built into every phase at WorkRoot IT Solutions: OWASP-aligned code reviews, encrypted data at rest and in transit, role-based access control, regular dependency audits, and GDPR- and SOC 2-style controls where a project demands them. The same architecture secures a Bihar government police platform deployed across 11 districts.',
},
];
+2 -2
View File
@@ -40,12 +40,12 @@ interface Props {
// credibility bug this set was created to fix.
//
// - "20+ Projects" — total delivered engagements
// - "11 Districts Deployed" matches hero trust badge copy
// - "11+ Districts Deployed" — cumulative Bihar government district reach
// - "50,000+ Records Managed" — records under management across deployments
// - "8+ Years Avg Experience" — average team experience
const defaultItems: ProofItem[] = [
{ value: '20+', label: 'Projects' },
{ value: '11', label: 'Districts Deployed' },
{ value: '11+', label: 'Districts Deployed' },
{ value: '50,000+', label: 'Records Managed' },
{ value: '8+', label: 'Years Avg Experience' },
];
+7 -7
View File
@@ -54,7 +54,7 @@ const orgId = `${baseUrl}/#organization`;
// src/pages/index.astro). All three are 5★, so the aggregate rating is 5.0
// over reviewCount = 3.
//
// Headline proof numbers in the description (20+ projects · 11 districts ·
// Headline proof numbers in the description (20+ projects · 11+ districts ·
// 50,000+ records · 99.9% uptime) mirror the canonical set in
// ProofNumbers.astro and the /services stats — keep them identical site-wide.
// ---------------------------------------------------------------------------
@@ -102,7 +102,7 @@ const organizationSchema = {
"height": "512"
},
"image": `${baseUrl}/og-image.jpg`,
"description": "WorkRoot IT Solutions LLP builds production-grade web, mobile, ERP, and government IT systems — 20+ projects delivered across 11 districts, 50,000+ records managed, and 99.9% uptime on mission-critical government deployments.",
"description": "WorkRoot IT Solutions LLP builds production-grade web, mobile, ERP, and government IT systems — 20+ projects delivered across 11+ districts, 50,000+ records managed, and 99.9% uptime on mission-critical government deployments.",
"foundingDate": "2022",
"founder": {
"@type": "Person",
@@ -238,7 +238,7 @@ const blogPostingSchema = article && type === 'BlogPosting' ? {
"dateModified": article.dateModified || article.datePublished,
"publisher": {
"@type": "Organization",
"name": "WorkRoot IT Solutions",
"name": "WorkRoot IT Solutions LLP",
"url": baseUrl,
"logo": {
"@type": "ImageObject",
@@ -298,7 +298,7 @@ const webPageSchema = type === 'WebPage' && Astro.url.pathname !== '/' ? {
},
"about": {
"@type": "Organization",
"name": "WorkRoot IT Solutions"
"name": "WorkRoot IT Solutions LLP"
},
"primaryImageOfPage": {
"@type": "ImageObject",
@@ -330,7 +330,7 @@ const aboutPageSchema = type === 'AboutPage' ? {
"inLanguage": "en-US",
"mainEntity": {
"@type": "Organization",
"name": "WorkRoot IT Solutions",
"name": "WorkRoot IT Solutions LLP",
"url": baseUrl
}
} : null;
@@ -345,8 +345,8 @@ const contactPageSchema = type === 'ContactPage' ? {
"inLanguage": "en-US",
"mainEntity": {
"@type": "LocalBusiness",
"name": "WorkRoot IT Solutions",
"alternateName": "WorkRoot IT Solutions LLP",
"name": "WorkRoot IT Solutions LLP",
"alternateName": "WorkRoot IT Solutions",
"url": baseUrl,
"telephone": "+91-9561417403",
"email": "admin@workroot.in",
@@ -0,0 +1,125 @@
---
title: "GeM Empanelment Process for Software Vendors: A Step-by-Step 2026 Guide"
description: "A complete, India-specific walkthrough of how software and IT-services vendors get listed and sell on the Government e-Marketplace (GeM) — registration steps, document checklist, fees, timelines, and how the bidding process actually works."
seoTitle: "GeM Empanelment for Software Vendors (2026)"
seoDescription: "Step-by-step guide to GeM registration and empanelment for software vendors in India — documents, fees, timelines, OEM panel, bids vs catalogue, and common rejection reasons."
pubDate: 2026-06-19
heroImage: "/images/blog/govt-systems.jpg"
category: "Web Development"
tags: ["GeM", "Government IT", "Vendor Registration", "Public Procurement", "India", "Software Services"]
author:
name: "Ajay"
avatar: "/images/team/ajay.jpg"
draft: false
---
The Government e-Marketplace (GeM) is the single largest channel for selling software and IT services to Indian government bodies — central ministries, state departments, PSUs, and autonomous institutions all procure through it. If you build custom software, mobile apps, or IT services and you are not on GeM, you are invisible to a buyer base that spends tens of thousands of crores a year. This guide explains exactly how a software vendor gets onto GeM, what it costs, how long it takes, and how to actually win business once you are listed.
One clarification up front: GeM does not use the word "empanelment" the way traditional government tenders do. On GeM you **register as a Seller** and then **publish offerings** (products or, for software companies, *services*). When people say "GeM empanelment for software vendors," they mean getting your company registered, verified, and listed so government buyers can find and buy from you. We use both terms interchangeably below.
## Who Should Register: Product Vendor vs Service Provider
Software companies usually fit one or both of these seller types, and the distinction matters because the catalogue, documents, and bidding flow differ.
| Seller Type | What You Sell on GeM | Typical Software Company Fit |
|-------------|----------------------|------------------------------|
| **Product Seller** | Listed goods with a catalogue (e.g., software licences, devices) | Off-the-shelf software, SaaS licences, hardware bundles |
| **Service Provider** | Service categories with rate-card / bid-based pricing | Custom development, IT manpower, AMC, cloud, web design |
| **OEM** | Original manufacturer with an OEM panel | Companies whose own software product is resold by others |
Most custom-software firms register primarily as **Service Providers** under categories like "Software Development Services," "Web Site Design & Development," "IT Professional Outsourcing," and "Custom Bid for Services."
## Step-by-Step Registration Process
The end-to-end flow from sign-up to your first live listing typically looks like this:
1. **Create a Primary User account** on gem.gov.in using your business email and the Aadhaar or PAN of the authorised signatory. The primary user is legally the organisation's representative.
2. **Select your organisation type** — Proprietorship, Partnership, LLP, Private Limited, etc. This determines which constitution documents you upload.
3. **Complete organisation verification** by linking your **Udyam (MSME) registration** or **CIN**, and validating your **PAN** and **GSTIN**.
4. **Add your business profile** — office address, bank account (for payments), and authorised signatory details.
5. **Complete the GeM seller assessment** where applicable (especially for OEMs and certain service categories).
6. **List your offerings** — choose the right service category, fill the technical parameters, upload your rate card / documents, and publish.
7. **Set up the Caution Money / e-PBG** mechanism if you intend to bid on higher-value tenders (more below).
### Document Checklist
Have these ready before you start — missing or mismatched documents are the number-one cause of delay:
- PAN of the organisation and the authorised signatory
- GSTIN certificate
- Udyam (MSME) registration certificate (strongly recommended — unlocks exemptions)
- Constitution proof (Certificate of Incorporation / Partnership Deed / GST for proprietorship)
- Cancelled cheque or bank account proof matching the registered name
- Authorised signatory's Aadhaar (for e-sign / OTP verification)
- Income Tax Return acknowledgements (often needed to clear turnover-based bid criteria)
- Relevant certifications (ISO, CMMI, CERT-In) if your target tenders demand them
## Fees, Caution Money, and Timelines
A frequent question: *is GeM registration free?* Basic seller registration on GeM is **free**. The costs come later, when you transact.
| Item | 2026 Cost (Indicative) | Notes |
|------|------------------------|-------|
| Seller registration | ₹0 | No registration fee |
| Caution Money Deposit (CMD) | ₹5,000 ₹25,000 | Slab-based on annual GeM turnover; refundable |
| Transaction charge | ~0.30% 0.45% of order value | Levied on the seller above a threshold per order |
| e-PBG / EMD for bids | 2%3% of bid value (often exempt for MSME) | Tender-specific; bid security |
**Timeline:** with documents in order, organisation verification typically completes in **37 working days**, and you can publish your first listing the same week. In practice, factoring in document corrections and category approvals, most software vendors are live and bid-ready in **24 weeks**.
## Catalogue Listing vs Bidding: How You Actually Win
Getting listed is necessary but not sufficient. There are three ways government buyers actually buy from you on GeM, and your strategy should target the right one.
| Mode | How It Works | Best For |
|------|-------------|----------|
| **Direct Purchase** | Buyer purchases directly up to ₹25,000 (or category limit) | Small, fast, low-value services |
| **L1 / Comparison** | Buyer compares listed sellers and picks lowest qualifying price | Standard, well-defined service offerings |
| **Bid / Reverse Auction** | Formal tender with eligibility criteria; sellers bid | High-value custom projects and AMCs |
For custom software and ERP work, the **Bid / Reverse Auction** route is where the meaningful contracts live. A buyer publishes a bid with eligibility criteria (turnover, past performance, certifications), and registered sellers submit technical and financial bids. Reverse auctions then drive the price down to the lowest qualifying bidder, so your win depends on meeting eligibility *and* pricing intelligently — not just being cheapest.
## MSME and Startup Benefits Worth Claiming
If you are an MSME or a DPIIT-recognised startup, GeM gives you real, money-saving advantages that you should activate during registration:
- **Exemption from prior turnover and prior experience** criteria in many bids (under the Public Procurement Policy for MSEs).
- **EMD / bid-security exemption** in a large share of tenders.
- **Reserved procurement** — a mandated share of government purchases is earmarked for MSEs.
- **No registration fee** and reduced caution-money slabs.
Linking your Udyam registration during onboarding is what unlocks these — skipping it leaves money and eligibility on the table.
## Common Reasons Software Vendors Get Rejected or Delayed
Avoid these and you will clear verification on the first attempt:
- **Name mismatches** across PAN, GST, bank account, and Udyam — every record must match exactly.
- **Wrong service category** — listing under a category that does not match your offering causes buyer-side rejection.
- **Incomplete technical parameters** in the service listing, so buyers cannot find or compare you.
- **Not meeting turnover/experience eligibility** in a bid (and not claiming MSME exemption when eligible).
- **Outdated documents** — an expired GST or unrenewed Udyam certificate stalls everything.
## Frequently Asked Questions
**Is GeM registration mandatory to sell to the government?**
For most central and state government procurement of available goods and services, yes — buyers are directed to procure through GeM. If your offering exists on GeM, being registered is effectively required to compete.
**How long does GeM registration take for a software company?**
Organisation verification typically completes in 37 working days. Allowing for document corrections and category approvals, most software vendors are live and bid-ready within 24 weeks.
**Does it cost money to register on GeM?**
Basic seller registration is free. Costs arise later: a refundable caution-money deposit (₹5,000–₹25,000 by turnover slab), a small per-order transaction charge, and bid-specific securities (often waived for MSMEs).
**Can a startup or MSME sell on GeM without prior government experience?**
Yes. Under the MSE procurement policy and startup provisions, many bids exempt MSMEs and DPIIT-recognised startups from prior turnover and experience requirements — but you must link your Udyam/DPIIT registration to claim it.
**What's the difference between being listed and winning a contract?**
Listing makes you visible. Winning happens through Direct Purchase (small value), L1 comparison, or Bid/Reverse Auction (high value). For custom software and ERP contracts, you win by qualifying on eligibility criteria and competing in the bid — listing alone generates very few orders.
**Which categories should a custom software firm register under?**
Common fits include Software Development Services, Web Site Design & Development, IT Professional Outsourcing / Manpower, Cloud Services, and "Custom Bid for Services" for non-standard scopes. Register under every category that genuinely matches your delivery capability.
## Turning GeM Access Into Government Contracts
Registration is the entry ticket; winning is a discipline. The vendors who succeed on GeM keep their documents current, list under precise categories, claim every MSME benefit they qualify for, and price their bids with the reverse-auction dynamic in mind. WorkRoot has delivered software for Indian government bodies — including police and university systems across multiple states — and we understand both the procurement process and the engineering bar it demands. If you want help positioning for government IT work, [get in touch](/contact).
@@ -0,0 +1,119 @@
---
title: "How Much Does Custom ERP Cost in India? (2026 Pricing Guide)"
description: "A transparent, India-specific breakdown of what a custom ERP actually costs in 2026 — by scope, team, and deployment model — with real rupee ranges, a cost calculator framework, and hidden costs to budget for."
seoTitle: "Custom ERP Cost in India (2026 Guide)"
seoDescription: "What does a custom ERP cost in India in 2026? Real rupee ranges by scope and team, a cost-driver breakdown, build-vs-buy maths, and hidden costs to plan for."
pubDate: 2026-06-19
heroImage: "/images/blog/erp-systems.jpg"
category: "Web Development"
tags: ["ERP", "Custom Software", "Pricing", "India", "Enterprise", "Cost Estimation"]
author:
name: "Ajay"
avatar: "/images/team/ajay.jpg"
draft: false
---
"How much will it cost?" is the first question every buyer asks about custom ERP — and the honest answer is "it depends." But "it depends" is not a number you can put in a budget. So in this guide we turn the question into something usable: what a custom ERP actually costs in India in 2026, what drives those numbers up or down, and how to estimate your own project before you talk to a single vendor.
The short version: a focused single-department custom ERP in India typically lands between **₹6,00,000 and ₹18,00,000**, a multi-module mid-market system between **₹18,00,000 and ₹60,00,000**, and a large multi-tenant or government-grade platform between **₹60,00,000 and ₹2,50,00,000+**. The rest of this article explains how to find *your* number inside those ranges.
## What "Custom ERP" Actually Means for Pricing
Before any number makes sense, you have to define scope. "ERP" is an umbrella — your price depends entirely on which modules you actually build. A finance-and-inventory system for a trading firm is a completely different project from a 14-module university ERP with admissions, examinations, hostel, and payroll.
Custom ERP cost in India is driven by five things, in roughly this order of impact:
1. **Number and complexity of modules** — each module is a mini-application with its own data model, screens, and rules.
2. **Number of distinct user roles and permission rules** — RBAC complexity scales non-linearly.
3. **Integrations** — payment gateways, GST/e-invoicing, SMS/WhatsApp, biometric, Tally, government APIs.
4. **Deployment model** — single-tenant vs multi-tenant, cloud vs on-premise, offline resilience.
5. **Non-functional requirements** — audit logging, data residency, uptime SLA, concurrent-user load.
## 2026 Cost Ranges by Project Tier
These are realistic, all-in development ranges for an India-based engagement (Indian team rates, INR), covering design, development, testing, and first deployment. They exclude ongoing maintenance, which we cover separately below.
| Tier | Typical Use Case | Modules | Timeline | Cost Range (INR) |
|------|------------------|---------|----------|------------------|
| **Starter** | Single-department tool (inventory, billing, or HR) | 12 | 610 weeks | ₹6L ₹18L |
| **Growth** | Mid-market multi-department ERP | 47 | 47 months | ₹18L ₹60L |
| **Enterprise** | Multi-entity / multi-location ERP, complex RBAC | 814 | 814 months | ₹60L ₹1.5Cr |
| **Government / Platform** | Multi-tenant, offline-resilient, audit-heavy | 10+ | 1224 months | ₹1.5Cr ₹2.5Cr+ |
A useful rule of thumb in 2026: a well-defined, production-grade ERP module costs roughly **₹4,00,000 to ₹9,00,000** to build, depending on how many screens, workflows, and integrations it carries.
## The Biggest Cost Driver: The Team
Custom software is labour. The single largest line item in any ERP quote is engineering time, so blended day-rates dominate the total. Here is what Indian ERP development talent typically costs in 2026.
| Role | Junior (₹/month) | Mid (₹/month) | Senior (₹/month) |
|------|------------------|---------------|------------------|
| Backend Engineer | ₹45,000 ₹70,000 | ₹80,000 ₹1,40,000 | ₹1,60,000 ₹2,80,000 |
| Frontend Engineer | ₹40,000 ₹65,000 | ₹75,000 ₹1,30,000 | ₹1,50,000 ₹2,50,000 |
| QA / Test Engineer | ₹35,000 ₹55,000 | ₹60,000 ₹1,00,000 | ₹1,10,000 ₹1,80,000 |
| Project Manager | — | ₹90,000 ₹1,50,000 | ₹1,60,000 ₹3,00,000 |
| UI/UX Designer | ₹40,000 ₹60,000 | ₹70,000 ₹1,20,000 | ₹1,30,000 ₹2,20,000 |
A typical Growth-tier project runs a squad of 46 people for 5 months. At blended mid-level rates, that alone accounts for ₹25L–₹40L of the budget — which is why the Growth range sits where it does.
## Build vs Buy: When Custom Actually Pays Off
Custom is not always the right call. Off-the-shelf and SaaS ERPs (Zoho, TallyPrime, SAP Business One, Odoo) have real advantages for standard workflows. Here is the honest comparison for an Indian mid-market buyer.
| Factor | Custom ERP | SaaS / Off-the-Shelf |
|--------|-----------|----------------------|
| Upfront cost | ₹18L ₹60L+ | ₹0 ₹5L setup |
| Recurring cost | ~1520% of build/year (AMC) | ₹500 ₹3,000 per user / month |
| Fit to your process | Exact | Adapt your process to the tool |
| Time to first value | 37 months | Days to weeks |
| Data ownership | Full | Vendor-hosted |
| Scaling cost | Predictable (you own it) | Grows with every seat |
The crossover maths matters. A 100-user SaaS ERP at ₹1,500/user/month costs **₹18,00,000 per year** — recurring, forever. A custom build at ₹40,00,000 with ₹7,00,000/year AMC breaks even against that SaaS bill in roughly **2.5 years**, and is cheaper every year after. Custom wins when your headcount is large, your processes are genuinely non-standard, or data sovereignty is non-negotiable (common in government and BFSI).
## Hidden Costs Buyers Forget to Budget
The build quote is not the total cost of ownership. Plan for these:
- **Annual Maintenance Contract (AMC):** budget **1520% of build cost per year** for bug fixes, security patches, and minor enhancements.
- **Cloud hosting:** a mid-size ERP on AWS/Azure in the Mumbai region typically costs **₹15,000 ₹60,000/month** depending on load and redundancy.
- **Third-party APIs:** SMS (₹0.12–₹0.25/message), WhatsApp Business, payment gateway (~2% per transaction), GST e-invoicing add-ons.
- **Data migration:** cleaning and importing legacy data is often underestimated — budget **₹1,50,000 ₹6,00,000**.
- **Training and change management:** for 50+ users, this is a real line item, not an afterthought.
- **Post-launch enhancements:** the first three months after go-live always surface new requirements.
## A Simple Way to Estimate Your Own Cost
You can get within ±25% of a real quote with this back-of-envelope method:
1. **List your modules.** Count them honestly.
2. **Multiply by ₹4L–₹9L per module** depending on complexity (simple CRUD = lower; workflow + integrations = higher).
3. **Add 20% for integrations** if you need GST, payments, or government APIs.
4. **Add 15% for multi-tenant or offline-resilient** architecture.
5. **Add the first year of AMC** (1520%) for a true year-one budget.
Example: a 6-module mid-market ERP with payment + GST integration → (6 × ₹6L) = ₹36L, +20% integrations = ₹43.2L, +18% AMC = roughly **₹51L for year one**. That lands squarely in the Growth tier, which is the reality check you want before any vendor conversation.
## Frequently Asked Questions
**What is the minimum realistic budget for a custom ERP in India in 2026?**
For a genuinely custom, production-grade single-department system, expect a floor of around **₹6,00,000**. Below that you are buying a configured off-the-shelf tool or a prototype, not a custom ERP.
**How long does a custom ERP take to build?**
A Starter system ships in 610 weeks. A Growth-tier multi-module ERP takes 47 months. Enterprise and government platforms run 824 months. Timeline scales with module count and integration depth, not just budget.
**Is custom ERP cheaper than SaaS over time?**
Often, yes — above roughly 75100 users or 2.5 years of use. A custom build has a high upfront cost but a flat recurring cost (AMC), while SaaS charges per seat forever. The more you grow, the better custom looks.
**Why do ERP quotes vary so much between vendors?**
Because "ERP" is undefined until you fix scope. A ₹15L and a ₹60L quote are usually answering two different questions about modules, roles, and integrations. Always compare on a fixed, written scope — never on the headline number alone.
**What ongoing costs should I plan for after launch?**
Budget 1520% of build cost per year for AMC, plus cloud hosting (₹15K–₹60K/month for mid-size systems), third-party API usage, and periodic enhancements. Total cost of ownership over five years is typically 1.72x the initial build.
**Should I pay fixed-price or time-and-materials?**
Fixed-price works when scope is locked and well-documented. Time-and-materials fits evolving requirements and longer builds. For most mid-market ERPs, a hybrid — fixed-price per milestone with a change-request process — gives the best balance of predictability and flexibility.
## Getting an Accurate Number for Your Project
The ranges above will get your budget into the right order of magnitude, but every project has specifics — a single complex integration or a strict uptime SLA can move the number significantly. At WorkRoot we have delivered ERP and management systems across Indian government and enterprise clients, and we are happy to turn your requirements into a fixed, written estimate. [Tell us about your project](/contact) and we will give you a real number, not a vague "it depends."
+11
View File
@@ -181,6 +181,17 @@ const fullTitle = title === 'Home' ? homeTitle : `${title} | ${brand}`;
<!-- Analytics (GA4 or Plausible) -->
<Analytics />
<!-- WRNexus Web Push SDK — loaded once globally so it runs on every page
that should collect push subscribers. is:inline keeps Astro's bundler
from rewriting the external src and preserves data-site-id / defer. -->
<script is:inline src="https://api.wrnexus.com/push/sdk.js" data-site-id="pk_rP51XN8tZnud5HdX9UqidjHk" defer></script>
<!-- WRNexus Bot Widget — loaded once globally here so the chat widget
renders on every route (pages + blog posts) via this shared layout.
is:inline keeps Astro's bundler from rewriting the external src and
preserves data-bot-id / async. -->
<script is:inline src="https://api.wrnexus.com/bots/widget.js" data-bot-id="d3998f1b-51d7-4f7c-a57e-3fa1af4394a0" async></script>
<!-- Additional head content slot -->
<slot name="head" />
</head>
+6 -4
View File
@@ -155,11 +155,13 @@ export const onRequest = defineMiddleware(async (context, next) => {
// - inline scripts/styles: Required for Astro framework
// - https://www.googletagmanager.com & https://www.google-analytics.com: GA4
// - https://plausible.io: Plausible Analytics
// - https://api.wrnexus.com: WRNexus web-push SDK (sdk.js), bot widget
// (bots/widget.js) + runtime config/iframe served from the same origin
// Note: unsafe-eval is removed in production (only needed during Astro HMR dev mode)
const isProduction = import.meta.env.PROD;
const scriptSrc = isProduction
? "script-src 'self' 'unsafe-inline' https://www.googletagmanager.com https://www.google-analytics.com https://plausible.io"
: "script-src 'self' 'unsafe-inline' 'unsafe-eval' https://www.googletagmanager.com https://www.google-analytics.com https://plausible.io";
? "script-src 'self' 'unsafe-inline' https://www.googletagmanager.com https://www.google-analytics.com https://plausible.io https://api.wrnexus.com"
: "script-src 'self' 'unsafe-inline' 'unsafe-eval' https://www.googletagmanager.com https://www.google-analytics.com https://plausible.io https://api.wrnexus.com";
const csp = [
"default-src 'self'",
@@ -167,11 +169,11 @@ export const onRequest = defineMiddleware(async (context, next) => {
"style-src 'self' 'unsafe-inline' https://fonts.googleapis.com",
"img-src 'self' data: https://images.unsplash.com https://www.google-analytics.com https://www.googletagmanager.com https://maps.googleapis.com https://maps.gstatic.com",
"font-src 'self' data: https://fonts.gstatic.com",
"connect-src 'self' https://www.google-analytics.com https://analytics.google.com https://stats.g.doubleclick.net https://plausible.io",
"connect-src 'self' https://www.google-analytics.com https://analytics.google.com https://stats.g.doubleclick.net https://plausible.io https://api.wrnexus.com",
"worker-src 'self'",
"manifest-src 'self'",
"object-src 'none'", // Block Flash/plugins (defense-in-depth)
"frame-src https://www.google.com", // Allow Google Maps iframe embed
"frame-src https://www.google.com https://api.wrnexus.com", // Google Maps iframe + WRNexus bot widget iframe
"frame-ancestors 'none'",
"base-uri 'self'",
"form-action 'self'",
+1 -1
View File
@@ -49,7 +49,7 @@ const teamMembers = [
// stats counters) so the numbers never contradict across pages.
const stats = [
{ value: '20+', label: 'Projects' },
{ value: '11', label: 'Districts Deployed' },
{ value: '11+', label: 'Districts Deployed' },
{ value: '50,000+', label: 'Records Managed' },
{ value: '8+', label: 'Years Avg Experience' },
];
+108 -3
View File
@@ -124,7 +124,7 @@ const testimonials = [
];
// NOTE: The homepage stat band is rendered once by <ProofNumbers />, which
// holds the canonical site-wide proof set (20+ Projects · 11 Districts ·
// holds the canonical site-wide proof set (20+ Projects · 11+ Districts ·
// 50,000+ Records · 8+ Years). The previously-duplicated "Numbers That
// Matter" stats section (and its local `stats` array + count-up script) was
// removed so a visitor never sees the same four numbers twice on one page.
@@ -171,11 +171,42 @@ const techStack = [
// "Common Questions" block that used to live above the tech-stack section
// was removed; its unique questions were merged into HomepageFaq's
// `moneyFaqs` array so no buyer-facing content was dropped.
// Quotable, self-contained company facts. Each sentence deliberately bundles
// the full company name + a concrete claim + a number so the line stays
// accurate and citable when lifted out of context (search snippets, press
// quotes, AI assistants). Every figure here is the canonical site-wide value
// and MUST stay identical to <ProofNumbers />, the hero trust badge, /about,
// and the /services pages. Do not change a number here without updating it
// everywhere it appears — mismatched stats reintroduce the credibility bug
// this canonical set was created to fix.
const companyFacts = [
'WorkRoot IT Solutions was founded in 2022 and has delivered 20+ production software systems for startups, enterprises, and government bodies.',
'WorkRoot IT Solutions runs government platforms across 11+ districts of Bihar, India, sustaining 99.9% uptime on mission-critical public services.',
'WorkRoot IT Solutions manages 50,000+ live records across its government and enterprise deployments.',
'WorkRoot IT Solutions built the University Management System for IIT Patna on a Next.js and Spring Boot stack.',
'WorkRoot IT Solutions is empanelled on the Government e-Marketplace (GeM), making it eligible to bid on Indian government IT tenders.',
'WorkRoot IT Solutions delivered a Police Management System that digitised FIRs and case tracking for 15,000+ officers across 11 districts of Bihar.',
'WorkRoot IT Solutions is a senior in-house engineering team averaging 8+ years of experience per engineer.',
];
// Comparison table — custom software (WorkRoot) vs off-the-shelf ERP / SaaS.
// Mirrors the deeper table on /services/custom-erp-development, surfaced on the
// homepage so the core buying trade-off is framed before a late-stage visitor
// reaches the inline lead form. The final row carries the canonical
// "11+ districts · 99.9% uptime" proof point.
const comparison = [
{ dimension: 'Fit to your workflow', offShelf: 'You reshape your process to fit the software', custom: 'The software is built around your exact process' },
{ dimension: 'Source-code ownership', offShelf: 'Vendor-locked, closed source', custom: 'Full IP transfer on delivery — you own the code' },
{ dimension: 'Licensing cost', offShelf: 'Recurring per-seat fees that grow with headcount', custom: 'One-time build — no per-seat licence tax' },
{ dimension: 'Data residency', offShelf: 'The vendors cloud and region', custom: 'Your servers, your region, your control' },
{ dimension: 'Proven at scale', offShelf: 'Generic, one-size-fits-all', custom: 'Live across 11+ districts at 99.9% uptime' },
];
---
<BaseLayout
title="Home"
description="WorkRoot IT Solutions builds production-grade web, mobile, ERP, and government IT systems. 20+ projects delivered across 11 districts with 99.9% uptime."
description="WorkRoot IT Solutions builds production-grade web, mobile, ERP, and government IT systems. 20+ projects delivered across 11+ districts with 99.9% uptime."
>
<SEO
slot="head"
@@ -208,7 +239,7 @@ const techStack = [
<svg class="w-5 h-5 text-accent-400" fill="currentColor" viewBox="0 0 20 20" aria-hidden="true">
<path fill-rule="evenodd" d="M6.267 3.455a3.066 3.066 0 001.745-.723 3.066 3.066 0 013.976 0 3.066 3.066 0 001.745.723 3.066 3.066 0 012.812 2.812c.051.643.304 1.254.723 1.745a3.066 3.066 0 010 3.976 3.066 3.066 0 00-.723 1.745 3.066 3.066 0 01-2.812 2.812 3.066 3.066 0 00-1.745.723 3.066 3.066 0 01-3.976 0 3.066 3.066 0 00-1.745-.723 3.066 3.066 0 01-2.812-2.812 3.066 3.066 0 00-.723-1.745 3.066 3.066 0 010-3.976 3.066 3.066 0 00.723-1.745 3.066 3.066 0 012.812-2.812zm7.44 5.252a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clip-rule="evenodd"/>
</svg>
<span class="text-sm font-medium text-white">Trusted by 20+ teams · Deployed across 11 districts</span>
<span class="text-sm font-medium text-white">Trusted by 20+ teams · Deployed across 11+ districts</span>
</div>
<!-- Main heading — benefit-led: outcome + audience -->
@@ -681,6 +712,80 @@ const techStack = [
</div>
</section>
<!-- Company Facts + Comparison — extractable, self-contained statements
that bundle the WorkRoot name with a concrete claim and a number so each
line stays accurate when quoted out of context (search snippets, press,
AI assistants). Followed by a custom-vs-off-the-shelf comparison table so
the buying trade-off is framed in one glance. Every figure matches the
canonical set in <ProofNumbers />. -->
<section
id="company-facts"
class="py-24 bg-white dark:bg-secondary-900 border-t border-secondary-100 dark:border-secondary-800"
aria-labelledby="company-facts-heading"
>
<div class="container-wrapper">
<div class="text-center max-w-3xl mx-auto mb-16" data-animate="fade-up">
<span class="inline-block px-4 py-2 rounded-full bg-primary-50 dark:bg-primary-900/30 text-primary-700 dark:text-primary-300 text-sm font-bold uppercase tracking-wider mb-4">The facts</span>
<h2 id="company-facts-heading" class="text-4xl sm:text-5xl font-bold text-secondary-900 dark:text-secondary-100 mb-6">WorkRoot at a glance</h2>
<p class="text-xl text-secondary-600 dark:text-secondary-400">
The record behind the work, stated plainly — so you know exactly who you're hiring.
</p>
</div>
<ul class="grid grid-cols-1 md:grid-cols-2 gap-4 sm:gap-5 max-w-5xl mx-auto mb-20" role="list">
{companyFacts.map((fact, index) => (
<li
class="flex items-start gap-4 bg-secondary-50 dark:bg-secondary-800/60 rounded-2xl border border-secondary-100 dark:border-secondary-700 p-5 sm:p-6"
data-animate="fade-up"
data-delay={String((index % 2) * 100)}
>
<span class="flex-shrink-0 w-10 h-10 rounded-xl bg-primary-100 dark:bg-primary-900/40 flex items-center justify-center" aria-hidden="true">
<svg class="w-6 h-6 text-primary-600 dark:text-primary-300" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2.2">
<path stroke-linecap="round" stroke-linejoin="round" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
</span>
<p class="text-base sm:text-lg text-secondary-800 dark:text-secondary-200 leading-relaxed">{fact}</p>
</li>
))}
</ul>
<!-- Comparison table: custom build vs off-the-shelf. Reuses the markup
pattern from /services/custom-erp-development for visual consistency. -->
<div class="max-w-4xl mx-auto" data-animate="fade-up">
<div class="text-center mb-8">
<h3 class="text-2xl sm:text-3xl font-bold text-secondary-900 dark:text-secondary-100 mb-3">Custom software vs off-the-shelf ERP</h3>
<p class="text-secondary-600 dark:text-secondary-400 text-base sm:text-lg">Where a bespoke WorkRoot build pays you back over a packaged SaaS tool.</p>
</div>
<div class="overflow-x-auto rounded-2xl border border-secondary-200 dark:border-secondary-700 shadow-sm">
<table class="w-full text-left border-collapse min-w-[640px]">
<caption class="sr-only">Comparison of off-the-shelf ERP software versus custom software built by WorkRoot IT Solutions</caption>
<thead>
<tr class="bg-secondary-50 dark:bg-secondary-800">
<th scope="col" class="px-5 py-4 text-sm font-bold text-secondary-700 dark:text-secondary-200">Dimension</th>
<th scope="col" class="px-5 py-4 text-sm font-bold text-secondary-500 dark:text-secondary-400">Off-the-shelf ERP</th>
<th scope="col" class="px-5 py-4 text-sm font-bold text-primary-700 dark:text-primary-300">Custom software (WorkRoot)</th>
</tr>
</thead>
<tbody>
{comparison.map((row, i) => (
<tr class={i % 2 === 0 ? 'bg-white dark:bg-secondary-900' : 'bg-secondary-50/50 dark:bg-secondary-800/40'}>
<th scope="row" class="px-5 py-4 text-sm font-semibold text-secondary-900 dark:text-secondary-100 align-top">{row.dimension}</th>
<td class="px-5 py-4 text-sm text-secondary-600 dark:text-secondary-400 align-top">{row.offShelf}</td>
<td class="px-5 py-4 text-sm text-secondary-700 dark:text-secondary-200 align-top font-medium">
<span class="inline-flex items-start gap-2">
<svg class="w-4 h-4 mt-0.5 flex-shrink-0 text-emerald-500" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2.5" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" d="M5 13l4 4L19 7" /></svg>
{row.custom}
</span>
</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
</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
+1 -1
View File
@@ -230,7 +230,7 @@ const featuredProjects = projects.filter(p => p.featured);
<div class="mt-14 grid grid-cols-2 sm:grid-cols-4 gap-4 lg:gap-6 reveal-on-scroll reveal-delay-2">
{[
{ value: '20+', label: 'Projects' },
{ value: '11', label: 'Districts Deployed' },
{ value: '11+', label: 'Districts Deployed' },
{ value: '50,000+', label: 'Records Managed' },
{ value: '8+', label: 'Years Avg Experience' },
].map((stat) => (
+1 -1
View File
@@ -294,7 +294,7 @@ const processSteps = [
<BaseLayout
title="ERP, Web & Mobile App Development Services"
description="Custom web development, mobile apps, ERP platforms, and government IT solutions from WorkRoot. 20+ projects delivered across 11 districts — get a free quote."
description="Custom web development, mobile apps, ERP platforms, and government IT solutions from WorkRoot. 20+ projects delivered across 11+ districts — get a free quote."
>
<SEO
slot="head"
@@ -4,7 +4,7 @@
// ServiceFaq) and feeds Service + FAQ schema into SEO.astro.
//
// All company stats mirror the canonical site-wide set used in
// ProofNumbers.astro, /portfolio, and SEO.astro — 20+ projects · 11 districts ·
// ProofNumbers.astro, /portfolio, and SEO.astro — 20+ projects · 11+ districts ·
// 50,000+ records · 99.9% uptime · 8+ years. Case-study numbers are taken
// verbatim from the IIT Patna and Bihar government case studies on /portfolio.
import BaseLayout from '../../layouts/BaseLayout.astro';
@@ -44,7 +44,7 @@ const comparison = [
const stats = [
{ value: '5+', label: 'ERP & enterprise deployments' },
{ value: '20,000+', label: 'Users across systems' },
{ value: '11', label: 'Districts running our platforms' },
{ value: '11+', label: 'Districts running our platforms' },
{ value: '99.9%', label: 'Uptime on critical systems' },
];
@@ -4,7 +4,9 @@
// schema into SEO.astro.
//
// Canonical stats mirror ProofNumbers.astro / /portfolio / SEO.astro:
// 20+ projects · 11 districts · 50,000+ records · 99.9% uptime · 8+ years.
// 20+ projects · 11+ districts · 50,000+ records · 99.9% uptime · 8+ years.
// (This page's own hero stat shows "11" — the specific Bihar deployment
// underpinning the company-wide "11+" figure; do not inflate it.)
import BaseLayout from '../../layouts/BaseLayout.astro';
import SEO from '../../components/SEO.astro';
import GuaranteeBand from '../../components/GuaranteeBand.astro';
+1 -1
View File
@@ -35,7 +35,7 @@ const sitemapSections = [
links: [
{ name: 'Custom ERP Development', url: '/services/custom-erp-development', description: 'Bespoke enterprise systems on Spring Boot & Angular' },
{ name: 'University ERP', url: '/services/university-erp', description: 'Admissions, exams, grading — built for IIT Patna' },
{ name: 'Government IT & GeM Vendor', url: '/services/government-it-solutions-gem-vendor', description: 'GeM-empanelled, live across 11 districts' },
{ name: 'Government IT & GeM Vendor', url: '/services/government-it-solutions-gem-vendor', description: 'GeM-empanelled, live across 11+ districts' },
{ name: 'Police Management System', url: '/services/police-management-system', description: 'FIR, case tracking & personnel for 15,000+ officers' },
{ name: 'React Native App Development', url: '/services/react-native-app-development', description: 'One codebase for iOS and Android' },
],
+2 -2
View File
@@ -84,7 +84,7 @@ export function generateArticleSchema(
},
publisher: {
'@type': 'Organization',
name: 'WorkRoot IT Solutions',
name: 'WorkRoot IT Solutions LLP',
logo: {
'@type': 'ImageObject',
url: 'https://workroot.in/logo.png',
@@ -132,7 +132,7 @@ export function generateServiceSchema(
description: description,
provider: {
'@type': 'Organization',
name: 'WorkRoot IT Solutions',
name: 'WorkRoot IT Solutions LLP',
url: siteUrl,
},
areaServed: {