Compare commits

..
34 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
platform-mcpandClaude Opus 4.8 22251ea82e feat(services): add keyword-focused service landing pages
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 13m25s
Add five SEO landing pages under /services that reuse existing section
components (GuaranteeBand, InlineLeadForm) and a new prop-driven ServiceFaq:

- /services/custom-erp-development
- /services/university-erp
- /services/government-it-solutions-gem-vendor
- /services/police-management-system
- /services/react-native-app-development

Each page has 1,000+ words of specific copy, a real portfolio-backed case
study (IIT Patna, PSCL Bihar, Police MS, Pophealth), a comparison table /
stat block, a page-specific FAQ that feeds the FAQPage schema, and a CTA into
the existing inline lead form (/api/contact). Service + FAQ + BreadcrumbList
schema are passed into SEO.astro; the visible FAQ accordion does not duplicate
the JSON-LD. All company stats match the canonical site-wide set.

Wire the pages into navigation: deep-dive links from the /services detail
sections, a new "Solutions" group on the HTML sitemap, and entries in both the
dynamic sitemap.xml.ts and the served static public/sitemap.xml.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-19 09:11:40 +05:30
platform-mcp a21aa99e90 pre-agent: Build keyword-focused service and location landing 2026-06-19 08:58:06 +05:30
platform-mcp e697750e22 agent: Set unique titles and meta descriptions per page 2026-06-19 08:57:40 +05:30
platform-mcp 23acac5c4d pre-agent: Set unique titles and meta descriptions per page 2026-06-19 08:48:19 +05:30
platform-mcpandClaude Opus 4.8 dccc5d3b7f feat(seo): centralize sitewide JSON-LD in SEO.astro with Organization reviews
E2E Test Suite / Smoke Tests (P0) (push) Failing after 9m27s
E2E Test Suite / Critical User Journeys (push) Has been skipped
E2E Test Suite / API Integration Tests (push) Has been skipped
E2E Test Suite / Security Header Tests (push) Failing after 10m21s
E2E Test Suite / Mobile Device Tests (push) Failing after 10m27s
E2E Test Suite / Cross-Browser Regression (webkit) (push) Failing after 11m13s
E2E Test Suite / Cross-Browser Regression (firefox) (push) Failing after 12m5s
E2E Test Suite / Cross-Browser Regression (chromium) (push) Failing after 12m58s
E2E Test Suite / Destructive & Chaos Tests (push) Failing after 13m50s
E2E Test Suite / Form Interaction Tests (push) Failing after 14m43s
E2E Test Suite / Test Report Summary (push) Has been cancelled
Deploy to Production / Build & Verify (push) Waiting to run
Deploy to Production / Pre-Deploy Tests (push) Blocked by required conditions
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
Move the base Organization + LocalBusiness and WebSite structured data out of
BaseLayout's inline scripts into SEO.astro behind a new `base` prop, rendered
once from BaseLayout's <head> so every route (incl. pages without a
page-specific <SEO>) carries the canonical company schema exactly once.

- Organization/LocalBusiness now includes aggregateRating + Review nodes built
  from the three real homepage testimonials (Ajay Pruthi, Dhirendra Singh,
  Dilip Raj — all 5-star, reviewCount 3, rating 5.0).
- foundingDate 2022, telephone +91-9561417403, email admin@workroot.in,
  areaServed Bihar (AdministrativeArea) + India (Country).
- Description keeps the canonical proof numbers identical to the site:
  20+ projects, 11 districts, 50,000+ records, 99.9% uptime.
- Reusable Service node now links provider via @id to #organization and
  serves Bihar/India; page-specific schema (Service/FAQ/WebPage/...) stays
  opt-in and is gated off the base instance to prevent duplication.
- Homepage still emits a single FAQPage (from HomepageFaq) — no duplicate.
- Add jsdom devDependency required by scripts/validate-schema.js.

Verified: build succeeds; scripts/validate-schema.js passes (24 schemas, 0
issues, 0 warnings); rendered ld+json inspected on homepage, services, and
schema-less pages (privacy/terms/cookies/404).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-19 08:47:50 +05:30
platform-mcp be80d63f04 pre-agent: Add sitewide JSON-LD structured data 2026-06-19 08:40:21 +05:30
platform-agentandClaude Opus 4.8 95a5af1d9a feat(home): strengthen Government IT card with live proof points
E2E Test Suite / Cross-Browser Regression (chromium) (push) Failing after 10m28s
E2E Test Suite / Destructive & Chaos Tests (push) Failing after 11m20s
E2E Test Suite / Form Interaction Tests (push) Failing after 12m13s
Ping Search Engines / Notify Search Engines (push) Failing after 11m6s
E2E Test Suite / Security Header Tests (push) Failing after 11m59s
E2E Test Suite / Mobile Device Tests (push) Failing after 12m51s
E2E Test Suite / Cross-Browser Regression (webkit) (push) Failing after 13m43s
E2E Test Suite / Cross-Browser Regression (firefox) (push) Failing after 14m36s
E2E Test Suite / Smoke Tests (P0) (push) Failing after 21m13s
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 20m11s
E2E Test Suite / Test Report Summary (push) Failing after 13m11s
Deploy to Production / Pre-Deploy Tests (push) Failing after 14m58s
Deploy to Production / Deploy to Fly.io (push) Failing after 13m48s
Deploy to Production / Deploy to VPS (PM2) (push) Failing after 14m4s
Deploy to Production / Deploy to Render (push) Failing after 14m40s
Deploy to Production / Deploy to Railway (push) Failing after 14m56s
Deploy to Production / Post-Deploy Verification (push) Failing after 14m10s
Deploy to Production / Notify on Failure (push) Failing after 14m58s
Surface the existing, already-published Government IT credibility signals
on the homepage service card — 11+ districts live, 99.9% uptime, and GeM
empanelment (all already stated on /services and in the homepage FAQ) —
so the card matches the depth of the per-service quote CTA beside it.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-17 20:49:55 +05:30
WorkRoot AgentandClaude Opus 4.8 5a6db9c20f feat(homepage): expand inline lead form to name/email/phone/type/message
Ping Search Engines / Notify Search Engines (push) Successful in 3s
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
Deploy to Production / Pre-Deploy Tests (push) Failing after 10m58s
Deploy to Production / Build & Verify (push) Failing after 16m37s
Deploy to Production / Deploy to Fly.io (push) Failing after 14m3s
Deploy to Production / Deploy to VPS (PM2) (push) Failing after 14m3s
Deploy to Production / Deploy to Render (push) Failing after 14m55s
Deploy to Production / Deploy to Railway (push) Failing after 14m55s
Deploy to Production / Post-Deploy Verification (push) Failing after 13m32s
Deploy to Production / Notify on Failure (push) Failing after 13m51s
Rework the homepage InlineLeadForm from a single combined "email or phone"
contact field to an explicit 5-field layout — name, email (type=email),
phone (type=tel), project type and an optional message — so each field gets
the correct mobile keyboard and leads carry a real email + phone separately.

- Required fields kept to name + email + project type for a low-friction
  mobile flow; phone and message are optional.
- Email uses type=email + inputmode=email; phone uses type=tel + inputmode=tel
  for the numeric keypad. Message is an optional textarea; when blank the
  client synthesizes a >=10 char summary so the /api/contact validator passes.
- Still posts to the same /api/contact endpoint tagged source=homepage-inline,
  delivering leads exactly the same way the /contact page does (no page nav).
- Update inline-lead-form.spec.ts to the new field set and keyboards.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-17 20:33:26 +05:30
platform-mcp 870075b33f pre-agent: 🔧 Auto-fix: frontend
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 11m51s
2026-06-17 20:32:16 +05:30
platform-mcp 8b16729ab4 pre-agent: 🔧 Auto-fix: frontend 2026-06-17 20:32:15 +05:30
platform-mcp 1ab4dc1033 agent: Make header and hero section full width 2026-06-17 20:31:31 +05:30
platform-mcp a064260c55 agent: Fix and replace header logo with correct asset 2026-06-17 20:31:30 +05:30
platform-mcp a7b1ae4800 agent: Fix mobile menu full-screen overlay 2026-06-17 20:30:40 +05:30
88 changed files with 5126 additions and 574 deletions
+67 -38
View File
@@ -1,11 +1,11 @@
# Graph Report - 2e709500-79ab-403f-9ca0-3f027ac9f36e (2026-06-17) # Graph Report - 2e709500-79ab-403f-9ca0-3f027ac9f36e (2026-06-24)
## Corpus Check ## Corpus Check
- 59 files · ~448,077 words - 64 files · ~615,668 words
- Verdict: corpus is large enough that graph structure adds value. - Verdict: corpus is large enough that graph structure adds value.
## Summary ## Summary
- 539 nodes · 582 edges · 95 communities (87 shown, 8 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) - Extraction: 99% EXTRACTED · 1% INFERRED · 0% AMBIGUOUS · INFERRED: 4 edges (avg confidence: 0.8)
- Token cost: 0 input · 0 output - Token cost: 0 input · 0 output
@@ -50,8 +50,29 @@
- [[_COMMUNITY_Community 34|Community 34]] - [[_COMMUNITY_Community 34|Community 34]]
- [[_COMMUNITY_Community 35|Community 35]] - [[_COMMUNITY_Community 35|Community 35]]
- [[_COMMUNITY_Community 36|Community 36]] - [[_COMMUNITY_Community 36|Community 36]]
- [[_COMMUNITY_Community 41|Community 41]] - [[_COMMUNITY_Community 37|Community 37]]
- [[_COMMUNITY_Community 42|Community 42]] - [[_COMMUNITY_Community 38|Community 38]]
- [[_COMMUNITY_Community 44|Community 44]]
- [[_COMMUNITY_Community 45|Community 45]]
- [[_COMMUNITY_Community 46|Community 46]]
- [[_COMMUNITY_Community 47|Community 47]]
- [[_COMMUNITY_Community 48|Community 48]]
- [[_COMMUNITY_Community 49|Community 49]]
- [[_COMMUNITY_Community 50|Community 50]]
- [[_COMMUNITY_Community 51|Community 51]]
- [[_COMMUNITY_Community 52|Community 52]]
- [[_COMMUNITY_Community 53|Community 53]]
- [[_COMMUNITY_Community 54|Community 54]]
- [[_COMMUNITY_Community 55|Community 55]]
- [[_COMMUNITY_Community 56|Community 56]]
- [[_COMMUNITY_Community 57|Community 57]]
- [[_COMMUNITY_Community 58|Community 58]]
- [[_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) ## God Nodes (most connected - your core abstractions)
1. `Performance Testing Scripts` - 12 edges 1. `Performance Testing Scripts` - 12 edges
@@ -75,7 +96,7 @@
- `POST()` --calls--> `logApiRequest()` [INFERRED] - `POST()` --calls--> `logApiRequest()` [INFERRED]
src/pages/api/newsletter.ts → src/utils/logger.ts src/pages/api/newsletter.ts → src/utils/logger.ts
## Communities (95 total, 8 thin omitted) ## Communities (117 total, 27 thin omitted)
### Community 0 - "Community 0" ### Community 0 - "Community 0"
Cohesion: 0.05 Cohesion: 0.05
@@ -125,49 +146,49 @@ Nodes (11): Code Example, code:astro (---), code:javascript (// Traditional SPA:
Cohesion: 0.18 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) 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" ### Community 16 - "Community 16"
Cohesion: 0.43 Cohesion: 0.2
Nodes (6): trackCaseStudyView(), trackEvent(), trackExternalLink(), trackFormSubmit(), trackNewsletterSignup(), trackPortfolioFilter() 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" ### Community 18 - "Community 18"
Cohesion: 0.36 Cohesion: 0.22
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() 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" ### Community 20 - "Community 20"
Cohesion: 0.46 Cohesion: 0.43
Nodes (7): captureScreenshot(), ensureDir(), generateIndex(), main(), screenshotPath(), setTheme(), waitForPageReady() Nodes (6): trackCaseStudyView(), trackEvent(), trackExternalLink(), trackFormSubmit(), trackNewsletterSignup(), trackPortfolioFilter()
### Community 21 - "Community 21" ### Community 21 - "Community 21"
Cohesion: 0.36 Cohesion: 0.36
Nodes (4): deleteFromDB(), getAllFromDB(), openDB(), processSyncQueue() 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 22 - "Community 22" ### Community 22 - "Community 22"
Cohesion: 0.46 Cohesion: 0.46
Nodes (7): checkRateLimit(), corsHeaders(), escapeHtml(), OPTIONS(), POST(), sendContactEmail(), validateContactForm()
### Community 23 - "Community 23"
Cohesion: 0.43
Nodes (6): trackCaseStudyView(), trackEvent(), trackExternalLink(), trackFormSubmit(), trackNewsletterSignup(), trackPortfolioFilter()
### 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"
Cohesion: 0.46
Nodes (7): captureScreenshot(), ensureDir(), generateIndex(), main(), screenshotPath(), setTheme(), waitForPageReady() Nodes (7): captureScreenshot(), ensureDir(), generateIndex(), main(), screenshotPath(), setTheme(), waitForPageReady()
### Community 26 - "Community 26" ### Community 23 - "Community 23"
Cohesion: 0.36 Cohesion: 0.36
Nodes (4): deleteFromDB(), getAllFromDB(), openDB(), processSyncQueue() 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" ### Community 27 - "Community 27"
Cohesion: 0.48 Cohesion: 0.46
Nodes (5): initAllAnimations(), initButtonRipple(), initCounters(), initProgressBars(), initScrollReveal() Nodes (7): captureScreenshot(), ensureDir(), generateIndex(), main(), screenshotPath(), setTheme(), waitForPageReady()
### Community 28 - "Community 28" ### Community 28 - "Community 28"
Cohesion: 0.48 Cohesion: 0.36
Nodes (5): extractJsonLd(), main(), validateOpenGraph(), validatePage(), validateTwitterCard() Nodes (4): deleteFromDB(), getAllFromDB(), openDB(), processSyncQueue()
### Community 29 - "Community 29" ### Community 29 - "Community 29"
Cohesion: 0.48 Cohesion: 0.48
@@ -178,25 +199,33 @@ Cohesion: 0.48
Nodes (5): extractJsonLd(), main(), validateOpenGraph(), validatePage(), validateTwitterCard() Nodes (5): extractJsonLd(), main(), validateOpenGraph(), validatePage(), validateTwitterCard()
### Community 31 - "Community 31" ### Community 31 - "Community 31"
Cohesion: 0.47 Cohesion: 0.48
Nodes (5): main(), measure_response_time(), print_stats(), Measure response time for a URL over multiple iterations, Print statistics for response times 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" ### Community 33 - "Community 33"
Cohesion: 0.47 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 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" ### 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 Cohesion: 0.6
Nodes (3): formatUptime(), GET(), percentile() Nodes (3): formatUptime(), GET(), percentile()
### Community 36 - "Community 36" ### Community 38 - "Community 38"
Cohesion: 0.6 Cohesion: 0.6
Nodes (3): formatUptime(), GET(), percentile() Nodes (3): formatUptime(), GET(), percentile()
## Knowledge Gaps ## Knowledge Gaps
- **121 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` (+116 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. These have ≤1 connection - possible missing edges or undocumented components.
- **8 thin communities (<3 nodes) omitted from report** — run `graphify query` to explore isolated nodes. - **27 thin communities (<3 nodes) omitted from report** — run `graphify query` to explore isolated nodes.
## Suggested Questions ## Suggested Questions
_Questions this graph is uniquely positioned to answer:_ _Questions this graph is uniquely positioned to answer:_
@@ -204,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?** - **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._ _`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?** - **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?**
_121 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?** - **Should `Community 0` be split into smaller, more focused modules?**
_Cohesion score 0.05 - nodes in this community are weakly interconnected._ _Cohesion score 0.05 - nodes in this community are weakly interconnected._
- **Should `Community 1` be split into smaller, more focused modules?** - **Should `Community 1` be split into smaller, more focused modules?**
@@ -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_test_results_e2e_smoke_suite_smoke_suit_6ed4a_has_proper_structured_data_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/e2e-smoke-suite-Smoke-Suit-6ed4a--has-proper-structured-data-chromium/error-context.md", "source_location": "L1"}, {"id": "e2e_smoke_suite_smoke_suit_6ed4a_has_proper_structured_data_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/e2e-smoke-suite-Smoke-Suit-6ed4a--has-proper-structured-data-chromium/error-context.md", "source_location": "L1"}, {"id": "e2e_smoke_suite_smoke_suit_6ed4a_has_proper_structured_data_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/e2e-smoke-suite-Smoke-Suit-6ed4a--has-proper-structured-data-chromium/error-context.md", "source_location": "L3"}], "edges": [{"source": "home_agent_ai_agent_backend_workspace_2e709500_79ab_403f_9ca0_3f027ac9f36e_test_results_e2e_smoke_suite_smoke_suit_6ed4a_has_proper_structured_data_chromium_error_context_md", "target": "e2e_smoke_suite_smoke_suit_6ed4a_has_proper_structured_data_chromium_error_context_page_snapshot", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/test-results/e2e-smoke-suite-Smoke-Suit-6ed4a--has-proper-structured-data-chromium/error-context.md", "source_location": "L1", "weight": 1.0}, {"source": "e2e_smoke_suite_smoke_suit_6ed4a_has_proper_structured_data_chromium_error_context_page_snapshot", "target": "e2e_smoke_suite_smoke_suit_6ed4a_has_proper_structured_data_chromium_error_context_codeblock_1", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/test-results/e2e-smoke-suite-Smoke-Suit-6ed4a--has-proper-structured-data-chromium/error-context.md", "source_location": "L3", "weight": 1.0}], "input_tokens": 0, "output_tokens": 0}
@@ -0,0 +1 @@
{"nodes": [{"id": "home_agent_ai_agent_backend_workspace_2e709500_79ab_403f_9ca0_3f027ac9f36e_playwright_report_data_1639575cca1fe4171917a09f8f7cc59c33654911_md", "label": "1639575cca1fe4171917a09f8f7cc59c33654911.md", "file_type": "document", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/playwright-report/data/1639575cca1fe4171917a09f8f7cc59c33654911.md", "source_location": "L1"}, {"id": "data_1639575cca1fe4171917a09f8f7cc59c33654911_page_snapshot", "label": "Page snapshot", "file_type": "document", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/playwright-report/data/1639575cca1fe4171917a09f8f7cc59c33654911.md", "source_location": "L1"}, {"id": "data_1639575cca1fe4171917a09f8f7cc59c33654911_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/playwright-report/data/1639575cca1fe4171917a09f8f7cc59c33654911.md", "source_location": "L3"}], "edges": [{"source": "home_agent_ai_agent_backend_workspace_2e709500_79ab_403f_9ca0_3f027ac9f36e_playwright_report_data_1639575cca1fe4171917a09f8f7cc59c33654911_md", "target": "data_1639575cca1fe4171917a09f8f7cc59c33654911_page_snapshot", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/playwright-report/data/1639575cca1fe4171917a09f8f7cc59c33654911.md", "source_location": "L1", "weight": 1.0}, {"source": "data_1639575cca1fe4171917a09f8f7cc59c33654911_page_snapshot", "target": "data_1639575cca1fe4171917a09f8f7cc59c33654911_codeblock_1", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/playwright-report/data/1639575cca1fe4171917a09f8f7cc59c33654911.md", "source_location": "L3", "weight": 1.0}], "input_tokens": 0, "output_tokens": 0}
@@ -0,0 +1 @@
{"nodes": [{"id": "home_agent_ai_agent_backend_workspace_2e709500_79ab_403f_9ca0_3f027ac9f36e_test_results_e2e_smoke_suite_smoke_suit_8282a_mepage_has_proper_meta_tags_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/e2e-smoke-suite-Smoke-Suit-8282a-mepage-has-proper-meta-tags-chromium/error-context.md", "source_location": "L1"}, {"id": "e2e_smoke_suite_smoke_suit_8282a_mepage_has_proper_meta_tags_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/e2e-smoke-suite-Smoke-Suit-8282a-mepage-has-proper-meta-tags-chromium/error-context.md", "source_location": "L1"}, {"id": "e2e_smoke_suite_smoke_suit_8282a_mepage_has_proper_meta_tags_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/e2e-smoke-suite-Smoke-Suit-8282a-mepage-has-proper-meta-tags-chromium/error-context.md", "source_location": "L3"}], "edges": [{"source": "home_agent_ai_agent_backend_workspace_2e709500_79ab_403f_9ca0_3f027ac9f36e_test_results_e2e_smoke_suite_smoke_suit_8282a_mepage_has_proper_meta_tags_chromium_error_context_md", "target": "e2e_smoke_suite_smoke_suit_8282a_mepage_has_proper_meta_tags_chromium_error_context_page_snapshot", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/test-results/e2e-smoke-suite-Smoke-Suit-8282a-mepage-has-proper-meta-tags-chromium/error-context.md", "source_location": "L1", "weight": 1.0}, {"source": "e2e_smoke_suite_smoke_suit_8282a_mepage_has_proper_meta_tags_chromium_error_context_page_snapshot", "target": "e2e_smoke_suite_smoke_suit_8282a_mepage_has_proper_meta_tags_chromium_error_context_codeblock_1", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/test-results/e2e-smoke-suite-Smoke-Suit-8282a-mepage-has-proper-meta-tags-chromium/error-context.md", "source_location": "L3", "weight": 1.0}], "input_tokens": 0, "output_tokens": 0}
@@ -0,0 +1 @@
{"nodes": [{"id": "home_agent_ai_agent_backend_workspace_2e709500_79ab_403f_9ca0_3f027ac9f36e_playwright_report_data_a1c157260391dca389fbe38c6259c4efd286b319_md", "label": "a1c157260391dca389fbe38c6259c4efd286b319.md", "file_type": "document", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/playwright-report/data/a1c157260391dca389fbe38c6259c4efd286b319.md", "source_location": "L1"}, {"id": "data_a1c157260391dca389fbe38c6259c4efd286b319_page_snapshot", "label": "Page snapshot", "file_type": "document", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/playwright-report/data/a1c157260391dca389fbe38c6259c4efd286b319.md", "source_location": "L1"}, {"id": "data_a1c157260391dca389fbe38c6259c4efd286b319_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/playwright-report/data/a1c157260391dca389fbe38c6259c4efd286b319.md", "source_location": "L3"}], "edges": [{"source": "home_agent_ai_agent_backend_workspace_2e709500_79ab_403f_9ca0_3f027ac9f36e_playwright_report_data_a1c157260391dca389fbe38c6259c4efd286b319_md", "target": "data_a1c157260391dca389fbe38c6259c4efd286b319_page_snapshot", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/playwright-report/data/a1c157260391dca389fbe38c6259c4efd286b319.md", "source_location": "L1", "weight": 1.0}, {"source": "data_a1c157260391dca389fbe38c6259c4efd286b319_page_snapshot", "target": "data_a1c157260391dca389fbe38c6259c4efd286b319_codeblock_1", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/playwright-report/data/a1c157260391dca389fbe38c6259c4efd286b319.md", "source_location": "L3", "weight": 1.0}], "input_tokens": 0, "output_tokens": 0}
@@ -0,0 +1 @@
{"nodes": [{"id": "home_agent_ai_agent_backend_workspace_2e709500_79ab_403f_9ca0_3f027ac9f36e_playwright_report_data_448763de83b442e1e9a6c6e4e70870f8a7246e0a_md", "label": "448763de83b442e1e9a6c6e4e70870f8a7246e0a.md", "file_type": "document", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/playwright-report/data/448763de83b442e1e9a6c6e4e70870f8a7246e0a.md", "source_location": "L1"}, {"id": "data_448763de83b442e1e9a6c6e4e70870f8a7246e0a_page_snapshot", "label": "Page snapshot", "file_type": "document", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/playwright-report/data/448763de83b442e1e9a6c6e4e70870f8a7246e0a.md", "source_location": "L1"}, {"id": "data_448763de83b442e1e9a6c6e4e70870f8a7246e0a_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/playwright-report/data/448763de83b442e1e9a6c6e4e70870f8a7246e0a.md", "source_location": "L3"}], "edges": [{"source": "home_agent_ai_agent_backend_workspace_2e709500_79ab_403f_9ca0_3f027ac9f36e_playwright_report_data_448763de83b442e1e9a6c6e4e70870f8a7246e0a_md", "target": "data_448763de83b442e1e9a6c6e4e70870f8a7246e0a_page_snapshot", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/playwright-report/data/448763de83b442e1e9a6c6e4e70870f8a7246e0a.md", "source_location": "L1", "weight": 1.0}, {"source": "data_448763de83b442e1e9a6c6e4e70870f8a7246e0a_page_snapshot", "target": "data_448763de83b442e1e9a6c6e4e70870f8a7246e0a_codeblock_1", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/playwright-report/data/448763de83b442e1e9a6c6e4e70870f8a7246e0a.md", "source_location": "L3", "weight": 1.0}], "input_tokens": 0, "output_tokens": 0}
@@ -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_playwright_report_data_3e3d231801ce8a68ec884e518f6e46e7f3a8d6ff_md", "label": "3e3d231801ce8a68ec884e518f6e46e7f3a8d6ff.md", "file_type": "document", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/playwright-report/data/3e3d231801ce8a68ec884e518f6e46e7f3a8d6ff.md", "source_location": "L1"}, {"id": "data_3e3d231801ce8a68ec884e518f6e46e7f3a8d6ff_page_snapshot", "label": "Page snapshot", "file_type": "document", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/playwright-report/data/3e3d231801ce8a68ec884e518f6e46e7f3a8d6ff.md", "source_location": "L1"}, {"id": "data_3e3d231801ce8a68ec884e518f6e46e7f3a8d6ff_codeblock_1", "label": "code:yaml (- generic [ref=e1]:)", "file_type": "document", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/playwright-report/data/3e3d231801ce8a68ec884e518f6e46e7f3a8d6ff.md", "source_location": "L3"}], "edges": [{"source": "home_agent_ai_agent_backend_workspace_2e709500_79ab_403f_9ca0_3f027ac9f36e_playwright_report_data_3e3d231801ce8a68ec884e518f6e46e7f3a8d6ff_md", "target": "data_3e3d231801ce8a68ec884e518f6e46e7f3a8d6ff_page_snapshot", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/playwright-report/data/3e3d231801ce8a68ec884e518f6e46e7f3a8d6ff.md", "source_location": "L1", "weight": 1.0}, {"source": "data_3e3d231801ce8a68ec884e518f6e46e7f3a8d6ff_page_snapshot", "target": "data_3e3d231801ce8a68ec884e518f6e46e7f3a8d6ff_codeblock_1", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/playwright-report/data/3e3d231801ce8a68ec884e518f6e46e7f3a8d6ff.md", "source_location": "L3", "weight": 1.0}], "input_tokens": 0, "output_tokens": 0}
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_playwright_report_data_6a86fc05ec2c2ce701392e4df2db4d19695f2488_md", "label": "6a86fc05ec2c2ce701392e4df2db4d19695f2488.md", "file_type": "document", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/playwright-report/data/6a86fc05ec2c2ce701392e4df2db4d19695f2488.md", "source_location": "L1"}, {"id": "data_6a86fc05ec2c2ce701392e4df2db4d19695f2488_page_snapshot", "label": "Page snapshot", "file_type": "document", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/playwright-report/data/6a86fc05ec2c2ce701392e4df2db4d19695f2488.md", "source_location": "L1"}, {"id": "data_6a86fc05ec2c2ce701392e4df2db4d19695f2488_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/playwright-report/data/6a86fc05ec2c2ce701392e4df2db4d19695f2488.md", "source_location": "L3"}], "edges": [{"source": "home_agent_ai_agent_backend_workspace_2e709500_79ab_403f_9ca0_3f027ac9f36e_playwright_report_data_6a86fc05ec2c2ce701392e4df2db4d19695f2488_md", "target": "data_6a86fc05ec2c2ce701392e4df2db4d19695f2488_page_snapshot", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/playwright-report/data/6a86fc05ec2c2ce701392e4df2db4d19695f2488.md", "source_location": "L1", "weight": 1.0}, {"source": "data_6a86fc05ec2c2ce701392e4df2db4d19695f2488_page_snapshot", "target": "data_6a86fc05ec2c2ce701392e4df2db4d19695f2488_codeblock_1", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/playwright-report/data/6a86fc05ec2c2ce701392e4df2db4d19695f2488.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_test_results_e2e_smoke_suite_smoke_suit_7fcfd_ntact_form_submission_works_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/e2e-smoke-suite-Smoke-Suit-7fcfd-ntact-form-submission-works-chromium/error-context.md", "source_location": "L1"}, {"id": "e2e_smoke_suite_smoke_suit_7fcfd_ntact_form_submission_works_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/e2e-smoke-suite-Smoke-Suit-7fcfd-ntact-form-submission-works-chromium/error-context.md", "source_location": "L1"}, {"id": "e2e_smoke_suite_smoke_suit_7fcfd_ntact_form_submission_works_chromium_error_context_codeblock_1", "label": "code:yaml (- generic [ref=e1]:)", "file_type": "document", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/test-results/e2e-smoke-suite-Smoke-Suit-7fcfd-ntact-form-submission-works-chromium/error-context.md", "source_location": "L3"}], "edges": [{"source": "home_agent_ai_agent_backend_workspace_2e709500_79ab_403f_9ca0_3f027ac9f36e_test_results_e2e_smoke_suite_smoke_suit_7fcfd_ntact_form_submission_works_chromium_error_context_md", "target": "e2e_smoke_suite_smoke_suit_7fcfd_ntact_form_submission_works_chromium_error_context_page_snapshot", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/test-results/e2e-smoke-suite-Smoke-Suit-7fcfd-ntact-form-submission-works-chromium/error-context.md", "source_location": "L1", "weight": 1.0}, {"source": "e2e_smoke_suite_smoke_suit_7fcfd_ntact_form_submission_works_chromium_error_context_page_snapshot", "target": "e2e_smoke_suite_smoke_suit_7fcfd_ntact_form_submission_works_chromium_error_context_codeblock_1", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/test-results/e2e-smoke-suite-Smoke-Suit-7fcfd-ntact-form-submission-works-chromium/error-context.md", "source_location": "L3", "weight": 1.0}], "input_tokens": 0, "output_tokens": 0}
@@ -0,0 +1 @@
{"nodes": [{"id": "home_agent_ai_agent_backend_workspace_2e709500_79ab_403f_9ca0_3f027ac9f36e_playwright_report_data_e913a96525d005a5baae8ddb2fbea15bbc53abb1_md", "label": "e913a96525d005a5baae8ddb2fbea15bbc53abb1.md", "file_type": "document", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/playwright-report/data/e913a96525d005a5baae8ddb2fbea15bbc53abb1.md", "source_location": "L1"}, {"id": "data_e913a96525d005a5baae8ddb2fbea15bbc53abb1_page_snapshot", "label": "Page snapshot", "file_type": "document", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/playwright-report/data/e913a96525d005a5baae8ddb2fbea15bbc53abb1.md", "source_location": "L1"}, {"id": "data_e913a96525d005a5baae8ddb2fbea15bbc53abb1_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/playwright-report/data/e913a96525d005a5baae8ddb2fbea15bbc53abb1.md", "source_location": "L3"}], "edges": [{"source": "home_agent_ai_agent_backend_workspace_2e709500_79ab_403f_9ca0_3f027ac9f36e_playwright_report_data_e913a96525d005a5baae8ddb2fbea15bbc53abb1_md", "target": "data_e913a96525d005a5baae8ddb2fbea15bbc53abb1_page_snapshot", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/playwright-report/data/e913a96525d005a5baae8ddb2fbea15bbc53abb1.md", "source_location": "L1", "weight": 1.0}, {"source": "data_e913a96525d005a5baae8ddb2fbea15bbc53abb1_page_snapshot", "target": "data_e913a96525d005a5baae8ddb2fbea15bbc53abb1_codeblock_1", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/playwright-report/data/e913a96525d005a5baae8ddb2fbea15bbc53abb1.md", "source_location": "L3", "weight": 1.0}], "input_tokens": 0, "output_tokens": 0}
@@ -0,0 +1 @@
{"nodes": [{"id": "home_agent_ai_agent_backend_workspace_2e709500_79ab_403f_9ca0_3f027ac9f36e_playwright_report_data_cc0ed5549c339ad261436d0f63d13eb3259a9dde_md", "label": "cc0ed5549c339ad261436d0f63d13eb3259a9dde.md", "file_type": "document", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/playwright-report/data/cc0ed5549c339ad261436d0f63d13eb3259a9dde.md", "source_location": "L1"}, {"id": "data_cc0ed5549c339ad261436d0f63d13eb3259a9dde_page_snapshot", "label": "Page snapshot", "file_type": "document", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/playwright-report/data/cc0ed5549c339ad261436d0f63d13eb3259a9dde.md", "source_location": "L1"}, {"id": "data_cc0ed5549c339ad261436d0f63d13eb3259a9dde_codeblock_1", "label": "code:yaml (- generic [ref=e1]:)", "file_type": "document", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/playwright-report/data/cc0ed5549c339ad261436d0f63d13eb3259a9dde.md", "source_location": "L3"}], "edges": [{"source": "home_agent_ai_agent_backend_workspace_2e709500_79ab_403f_9ca0_3f027ac9f36e_playwright_report_data_cc0ed5549c339ad261436d0f63d13eb3259a9dde_md", "target": "data_cc0ed5549c339ad261436d0f63d13eb3259a9dde_page_snapshot", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/playwright-report/data/cc0ed5549c339ad261436d0f63d13eb3259a9dde.md", "source_location": "L1", "weight": 1.0}, {"source": "data_cc0ed5549c339ad261436d0f63d13eb3259a9dde_page_snapshot", "target": "data_cc0ed5549c339ad261436d0f63d13eb3259a9dde_codeblock_1", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/playwright-report/data/cc0ed5549c339ad261436d0f63d13eb3259a9dde.md", "source_location": "L3", "weight": 1.0}], "input_tokens": 0, "output_tokens": 0}
@@ -0,0 +1 @@
{"nodes": [{"id": "home_agent_ai_agent_backend_workspace_2e709500_79ab_403f_9ca0_3f027ac9f36e_playwright_report_data_690bd8f0a073eceeb87b8e46e681123950f08f93_md", "label": "690bd8f0a073eceeb87b8e46e681123950f08f93.md", "file_type": "document", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/playwright-report/data/690bd8f0a073eceeb87b8e46e681123950f08f93.md", "source_location": "L1"}, {"id": "data_690bd8f0a073eceeb87b8e46e681123950f08f93_page_snapshot", "label": "Page snapshot", "file_type": "document", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/playwright-report/data/690bd8f0a073eceeb87b8e46e681123950f08f93.md", "source_location": "L1"}, {"id": "data_690bd8f0a073eceeb87b8e46e681123950f08f93_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/playwright-report/data/690bd8f0a073eceeb87b8e46e681123950f08f93.md", "source_location": "L3"}], "edges": [{"source": "home_agent_ai_agent_backend_workspace_2e709500_79ab_403f_9ca0_3f027ac9f36e_playwright_report_data_690bd8f0a073eceeb87b8e46e681123950f08f93_md", "target": "data_690bd8f0a073eceeb87b8e46e681123950f08f93_page_snapshot", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/playwright-report/data/690bd8f0a073eceeb87b8e46e681123950f08f93.md", "source_location": "L1", "weight": 1.0}, {"source": "data_690bd8f0a073eceeb87b8e46e681123950f08f93_page_snapshot", "target": "data_690bd8f0a073eceeb87b8e46e681123950f08f93_codeblock_1", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/playwright-report/data/690bd8f0a073eceeb87b8e46e681123950f08f93.md", "source_location": "L3", "weight": 1.0}], "input_tokens": 0, "output_tokens": 0}
@@ -0,0 +1 @@
{"nodes": [{"id": "home_agent_ai_agent_backend_workspace_2e709500_79ab_403f_9ca0_3f027ac9f36e_playwright_report_data_806b690efd1ee745a1991725b2d2aea1b5c435f2_md", "label": "806b690efd1ee745a1991725b2d2aea1b5c435f2.md", "file_type": "document", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/playwright-report/data/806b690efd1ee745a1991725b2d2aea1b5c435f2.md", "source_location": "L1"}, {"id": "data_806b690efd1ee745a1991725b2d2aea1b5c435f2_page_snapshot", "label": "Page snapshot", "file_type": "document", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/playwright-report/data/806b690efd1ee745a1991725b2d2aea1b5c435f2.md", "source_location": "L1"}, {"id": "data_806b690efd1ee745a1991725b2d2aea1b5c435f2_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/playwright-report/data/806b690efd1ee745a1991725b2d2aea1b5c435f2.md", "source_location": "L3"}], "edges": [{"source": "home_agent_ai_agent_backend_workspace_2e709500_79ab_403f_9ca0_3f027ac9f36e_playwright_report_data_806b690efd1ee745a1991725b2d2aea1b5c435f2_md", "target": "data_806b690efd1ee745a1991725b2d2aea1b5c435f2_page_snapshot", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/playwright-report/data/806b690efd1ee745a1991725b2d2aea1b5c435f2.md", "source_location": "L1", "weight": 1.0}, {"source": "data_806b690efd1ee745a1991725b2d2aea1b5c435f2_page_snapshot", "target": "data_806b690efd1ee745a1991725b2d2aea1b5c435f2_codeblock_1", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/playwright-report/data/806b690efd1ee745a1991725b2d2aea1b5c435f2.md", "source_location": "L3", "weight": 1.0}], "input_tokens": 0, "output_tokens": 0}
@@ -0,0 +1 @@
{"nodes": [{"id": "home_agent_ai_agent_backend_workspace_2e709500_79ab_403f_9ca0_3f027ac9f36e_playwright_report_data_32e7238c826ab5be125e270cc69efc4a37a8ffb3_md", "label": "32e7238c826ab5be125e270cc69efc4a37a8ffb3.md", "file_type": "document", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/playwright-report/data/32e7238c826ab5be125e270cc69efc4a37a8ffb3.md", "source_location": "L1"}, {"id": "data_32e7238c826ab5be125e270cc69efc4a37a8ffb3_page_snapshot", "label": "Page snapshot", "file_type": "document", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/playwright-report/data/32e7238c826ab5be125e270cc69efc4a37a8ffb3.md", "source_location": "L1"}, {"id": "data_32e7238c826ab5be125e270cc69efc4a37a8ffb3_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/playwright-report/data/32e7238c826ab5be125e270cc69efc4a37a8ffb3.md", "source_location": "L3"}], "edges": [{"source": "home_agent_ai_agent_backend_workspace_2e709500_79ab_403f_9ca0_3f027ac9f36e_playwright_report_data_32e7238c826ab5be125e270cc69efc4a37a8ffb3_md", "target": "data_32e7238c826ab5be125e270cc69efc4a37a8ffb3_page_snapshot", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/playwright-report/data/32e7238c826ab5be125e270cc69efc4a37a8ffb3.md", "source_location": "L1", "weight": 1.0}, {"source": "data_32e7238c826ab5be125e270cc69efc4a37a8ffb3_page_snapshot", "target": "data_32e7238c826ab5be125e270cc69efc4a37a8ffb3_codeblock_1", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/playwright-report/data/32e7238c826ab5be125e270cc69efc4a37a8ffb3.md", "source_location": "L3", "weight": 1.0}], "input_tokens": 0, "output_tokens": 0}
@@ -0,0 +1 @@
{"nodes": [{"id": "home_agent_ai_agent_backend_workspace_2e709500_79ab_403f_9ca0_3f027ac9f36e_tests_inline_lead_form_spec_ts", "label": "inline-lead-form.spec.ts", "file_type": "code", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/tests/inline-lead-form.spec.ts", "source_location": "L1"}], "edges": [{"source": "home_agent_ai_agent_backend_workspace_2e709500_79ab_403f_9ca0_3f027ac9f36e_tests_inline_lead_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/inline-lead-form.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_report_data_c0712711f6dffbdf3e61da69d7bf618fa7d12cce_md", "label": "c0712711f6dffbdf3e61da69d7bf618fa7d12cce.md", "file_type": "document", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/playwright-report/data/c0712711f6dffbdf3e61da69d7bf618fa7d12cce.md", "source_location": "L1"}, {"id": "data_c0712711f6dffbdf3e61da69d7bf618fa7d12cce_page_snapshot", "label": "Page snapshot", "file_type": "document", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/playwright-report/data/c0712711f6dffbdf3e61da69d7bf618fa7d12cce.md", "source_location": "L1"}, {"id": "data_c0712711f6dffbdf3e61da69d7bf618fa7d12cce_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/playwright-report/data/c0712711f6dffbdf3e61da69d7bf618fa7d12cce.md", "source_location": "L3"}], "edges": [{"source": "home_agent_ai_agent_backend_workspace_2e709500_79ab_403f_9ca0_3f027ac9f36e_playwright_report_data_c0712711f6dffbdf3e61da69d7bf618fa7d12cce_md", "target": "data_c0712711f6dffbdf3e61da69d7bf618fa7d12cce_page_snapshot", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/playwright-report/data/c0712711f6dffbdf3e61da69d7bf618fa7d12cce.md", "source_location": "L1", "weight": 1.0}, {"source": "data_c0712711f6dffbdf3e61da69d7bf618fa7d12cce_page_snapshot", "target": "data_c0712711f6dffbdf3e61da69d7bf618fa7d12cce_codeblock_1", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/playwright-report/data/c0712711f6dffbdf3e61da69d7bf618fa7d12cce.md", "source_location": "L3", "weight": 1.0}], "input_tokens": 0, "output_tokens": 0}
@@ -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_playwright_report_data_0f6bc059f79105264bd8619a97b50f0c07ebb8d8_md", "label": "0f6bc059f79105264bd8619a97b50f0c07ebb8d8.md", "file_type": "document", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/playwright-report/data/0f6bc059f79105264bd8619a97b50f0c07ebb8d8.md", "source_location": "L1"}, {"id": "data_0f6bc059f79105264bd8619a97b50f0c07ebb8d8_page_snapshot", "label": "Page snapshot", "file_type": "document", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/playwright-report/data/0f6bc059f79105264bd8619a97b50f0c07ebb8d8.md", "source_location": "L1"}, {"id": "data_0f6bc059f79105264bd8619a97b50f0c07ebb8d8_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/playwright-report/data/0f6bc059f79105264bd8619a97b50f0c07ebb8d8.md", "source_location": "L3"}], "edges": [{"source": "home_agent_ai_agent_backend_workspace_2e709500_79ab_403f_9ca0_3f027ac9f36e_playwright_report_data_0f6bc059f79105264bd8619a97b50f0c07ebb8d8_md", "target": "data_0f6bc059f79105264bd8619a97b50f0c07ebb8d8_page_snapshot", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/playwright-report/data/0f6bc059f79105264bd8619a97b50f0c07ebb8d8.md", "source_location": "L1", "weight": 1.0}, {"source": "data_0f6bc059f79105264bd8619a97b50f0c07ebb8d8_page_snapshot", "target": "data_0f6bc059f79105264bd8619a97b50f0c07ebb8d8_codeblock_1", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/playwright-report/data/0f6bc059f79105264bd8619a97b50f0c07ebb8d8.md", "source_location": "L3", "weight": 1.0}], "input_tokens": 0, "output_tokens": 0}
@@ -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"}]}
@@ -0,0 +1 @@
{"nodes": [{"id": "home_agent_ai_agent_backend_workspace_2e709500_79ab_403f_9ca0_3f027ac9f36e_test_results_e2e_smoke_suite_smoke_suit_742f0_enders_correctly_on_desktop_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/e2e-smoke-suite-Smoke-Suit-742f0-enders-correctly-on-Desktop-chromium/error-context.md", "source_location": "L1"}, {"id": "e2e_smoke_suite_smoke_suit_742f0_enders_correctly_on_desktop_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/e2e-smoke-suite-Smoke-Suit-742f0-enders-correctly-on-Desktop-chromium/error-context.md", "source_location": "L1"}, {"id": "e2e_smoke_suite_smoke_suit_742f0_enders_correctly_on_desktop_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/e2e-smoke-suite-Smoke-Suit-742f0-enders-correctly-on-Desktop-chromium/error-context.md", "source_location": "L3"}], "edges": [{"source": "home_agent_ai_agent_backend_workspace_2e709500_79ab_403f_9ca0_3f027ac9f36e_test_results_e2e_smoke_suite_smoke_suit_742f0_enders_correctly_on_desktop_chromium_error_context_md", "target": "e2e_smoke_suite_smoke_suit_742f0_enders_correctly_on_desktop_chromium_error_context_page_snapshot", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/test-results/e2e-smoke-suite-Smoke-Suit-742f0-enders-correctly-on-Desktop-chromium/error-context.md", "source_location": "L1", "weight": 1.0}, {"source": "e2e_smoke_suite_smoke_suit_742f0_enders_correctly_on_desktop_chromium_error_context_page_snapshot", "target": "e2e_smoke_suite_smoke_suit_742f0_enders_correctly_on_desktop_chromium_error_context_codeblock_1", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/test-results/e2e-smoke-suite-Smoke-Suit-742f0-enders-correctly-on-Desktop-chromium/error-context.md", "source_location": "L3", "weight": 1.0}], "input_tokens": 0, "output_tokens": 0}
@@ -0,0 +1 @@
{"nodes": [{"id": "home_agent_ai_agent_backend_workspace_2e709500_79ab_403f_9ca0_3f027ac9f36e_playwright_report_data_bbfcf94d298e196a738e1c4aa9fcbebf09850749_md", "label": "bbfcf94d298e196a738e1c4aa9fcbebf09850749.md", "file_type": "document", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/playwright-report/data/bbfcf94d298e196a738e1c4aa9fcbebf09850749.md", "source_location": "L1"}, {"id": "data_bbfcf94d298e196a738e1c4aa9fcbebf09850749_page_snapshot", "label": "Page snapshot", "file_type": "document", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/playwright-report/data/bbfcf94d298e196a738e1c4aa9fcbebf09850749.md", "source_location": "L1"}, {"id": "data_bbfcf94d298e196a738e1c4aa9fcbebf09850749_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/playwright-report/data/bbfcf94d298e196a738e1c4aa9fcbebf09850749.md", "source_location": "L3"}], "edges": [{"source": "home_agent_ai_agent_backend_workspace_2e709500_79ab_403f_9ca0_3f027ac9f36e_playwright_report_data_bbfcf94d298e196a738e1c4aa9fcbebf09850749_md", "target": "data_bbfcf94d298e196a738e1c4aa9fcbebf09850749_page_snapshot", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/playwright-report/data/bbfcf94d298e196a738e1c4aa9fcbebf09850749.md", "source_location": "L1", "weight": 1.0}, {"source": "data_bbfcf94d298e196a738e1c4aa9fcbebf09850749_page_snapshot", "target": "data_bbfcf94d298e196a738e1c4aa9fcbebf09850749_codeblock_1", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/playwright-report/data/bbfcf94d298e196a738e1c4aa9fcbebf09850749.md", "source_location": "L3", "weight": 1.0}], "input_tokens": 0, "output_tokens": 0}
@@ -0,0 +1 @@
{"nodes": [{"id": "home_agent_ai_agent_backend_workspace_2e709500_79ab_403f_9ca0_3f027ac9f36e_playwright_report_data_6604b3783e14b0fe2e620b288e8828b6f55be330_md", "label": "6604b3783e14b0fe2e620b288e8828b6f55be330.md", "file_type": "document", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/playwright-report/data/6604b3783e14b0fe2e620b288e8828b6f55be330.md", "source_location": "L1"}, {"id": "data_6604b3783e14b0fe2e620b288e8828b6f55be330_page_snapshot", "label": "Page snapshot", "file_type": "document", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/playwright-report/data/6604b3783e14b0fe2e620b288e8828b6f55be330.md", "source_location": "L1"}, {"id": "data_6604b3783e14b0fe2e620b288e8828b6f55be330_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/playwright-report/data/6604b3783e14b0fe2e620b288e8828b6f55be330.md", "source_location": "L3"}], "edges": [{"source": "home_agent_ai_agent_backend_workspace_2e709500_79ab_403f_9ca0_3f027ac9f36e_playwright_report_data_6604b3783e14b0fe2e620b288e8828b6f55be330_md", "target": "data_6604b3783e14b0fe2e620b288e8828b6f55be330_page_snapshot", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/playwright-report/data/6604b3783e14b0fe2e620b288e8828b6f55be330.md", "source_location": "L1", "weight": 1.0}, {"source": "data_6604b3783e14b0fe2e620b288e8828b6f55be330_page_snapshot", "target": "data_6604b3783e14b0fe2e620b288e8828b6f55be330_codeblock_1", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/playwright-report/data/6604b3783e14b0fe2e620b288e8828b6f55be330.md", "source_location": "L3", "weight": 1.0}], "input_tokens": 0, "output_tokens": 0}
@@ -0,0 +1 @@
{"nodes": [{"id": "home_agent_ai_agent_backend_workspace_2e709500_79ab_403f_9ca0_3f027ac9f36e_playwright_report_data_9b3c49237ac0c6da58c2e4aee5df00459559a271_md", "label": "9b3c49237ac0c6da58c2e4aee5df00459559a271.md", "file_type": "document", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/playwright-report/data/9b3c49237ac0c6da58c2e4aee5df00459559a271.md", "source_location": "L1"}, {"id": "data_9b3c49237ac0c6da58c2e4aee5df00459559a271_page_snapshot", "label": "Page snapshot", "file_type": "document", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/playwright-report/data/9b3c49237ac0c6da58c2e4aee5df00459559a271.md", "source_location": "L1"}, {"id": "data_9b3c49237ac0c6da58c2e4aee5df00459559a271_codeblock_1", "label": "code:yaml (- generic [ref=e1]:)", "file_type": "document", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/playwright-report/data/9b3c49237ac0c6da58c2e4aee5df00459559a271.md", "source_location": "L3"}], "edges": [{"source": "home_agent_ai_agent_backend_workspace_2e709500_79ab_403f_9ca0_3f027ac9f36e_playwright_report_data_9b3c49237ac0c6da58c2e4aee5df00459559a271_md", "target": "data_9b3c49237ac0c6da58c2e4aee5df00459559a271_page_snapshot", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/playwright-report/data/9b3c49237ac0c6da58c2e4aee5df00459559a271.md", "source_location": "L1", "weight": 1.0}, {"source": "data_9b3c49237ac0c6da58c2e4aee5df00459559a271_page_snapshot", "target": "data_9b3c49237ac0c6da58c2e4aee5df00459559a271_codeblock_1", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/home/agent/ai-agent/backend/workspace/2e709500-79ab-403f-9ca0-3f027ac9f36e/playwright-report/data/9b3c49237ac0c6da58c2e4aee5df00459559a271.md", "source_location": "L3", "weight": 1.0}], "input_tokens": 0, "output_tokens": 0}
File diff suppressed because one or more lines are too long
+1549 -249
View File
File diff suppressed because it is too large Load Diff
+523
View File
@@ -22,6 +22,7 @@
"@astrojs/tailwind": "^5.1.0", "@astrojs/tailwind": "^5.1.0",
"@playwright/test": "^1.58.2", "@playwright/test": "^1.58.2",
"@types/nodemailer": "^7.0.11", "@types/nodemailer": "^7.0.11",
"jsdom": "^29.1.1",
"playwright": "^1.58.2", "playwright": "^1.58.2",
"tailwindcss": "^3.4.1", "tailwindcss": "^3.4.1",
"typescript": "^5.9.3" "typescript": "^5.9.3"
@@ -39,6 +40,57 @@
"url": "https://github.com/sponsors/sindresorhus" "url": "https://github.com/sponsors/sindresorhus"
} }
}, },
"node_modules/@asamuzakjp/css-color": {
"version": "5.1.11",
"resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-5.1.11.tgz",
"integrity": "sha512-KVw6qIiCTUQhByfTd78h2yD1/00waTmm9uy/R7Ck/ctUyAPj+AEDLkQIdJW0T8+qGgj3j5bpNKK7Q3G+LedJWg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@asamuzakjp/generational-cache": "^1.0.1",
"@csstools/css-calc": "^3.2.0",
"@csstools/css-color-parser": "^4.1.0",
"@csstools/css-parser-algorithms": "^4.0.0",
"@csstools/css-tokenizer": "^4.0.0"
},
"engines": {
"node": "^20.19.0 || ^22.12.0 || >=24.0.0"
}
},
"node_modules/@asamuzakjp/dom-selector": {
"version": "7.1.1",
"resolved": "https://registry.npmjs.org/@asamuzakjp/dom-selector/-/dom-selector-7.1.1.tgz",
"integrity": "sha512-67RZDnYRc8H/8MLDgQCDE//zoqVFwajkepHZgmXrbwybzXOEwOWGPYGmALYl9J2DOLfFPPs6kKCqmbzV895hTQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@asamuzakjp/generational-cache": "^1.0.1",
"@asamuzakjp/nwsapi": "^2.3.9",
"bidi-js": "^1.0.3",
"css-tree": "^3.2.1",
"is-potential-custom-element-name": "^1.0.1"
},
"engines": {
"node": "^20.19.0 || ^22.12.0 || >=24.0.0"
}
},
"node_modules/@asamuzakjp/generational-cache": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/@asamuzakjp/generational-cache/-/generational-cache-1.0.1.tgz",
"integrity": "sha512-wajfB8KqzMCN2KGNFdLkReeHncd0AslUSrvHVvvYWuU8ghncRJoA50kT3zP9MVL0+9g4/67H+cdvBskj9THPzg==",
"dev": true,
"license": "MIT",
"engines": {
"node": "^20.19.0 || ^22.12.0 || >=24.0.0"
}
},
"node_modules/@asamuzakjp/nwsapi": {
"version": "2.3.9",
"resolved": "https://registry.npmjs.org/@asamuzakjp/nwsapi/-/nwsapi-2.3.9.tgz",
"integrity": "sha512-n8GuYSrI9bF7FFZ/SjhwevlHc8xaVlb/7HmHelnc/PZXBD2ZR49NnN9sMMuDdEGPeeRQ5d0hqlSlEpgCX3Wl0Q==",
"dev": true,
"license": "MIT"
},
"node_modules/@astrojs/check": { "node_modules/@astrojs/check": {
"version": "0.9.9", "version": "0.9.9",
"resolved": "https://registry.npmjs.org/@astrojs/check/-/check-0.9.9.tgz", "resolved": "https://registry.npmjs.org/@astrojs/check/-/check-0.9.9.tgz",
@@ -554,6 +606,159 @@
"node": ">=6.9.0" "node": ">=6.9.0"
} }
}, },
"node_modules/@bramus/specificity": {
"version": "2.4.2",
"resolved": "https://registry.npmjs.org/@bramus/specificity/-/specificity-2.4.2.tgz",
"integrity": "sha512-ctxtJ/eA+t+6q2++vj5j7FYX3nRu311q1wfYH3xjlLOsczhlhxAg2FWNUXhpGvAw3BWo1xBcvOV6/YLc2r5FJw==",
"dev": true,
"license": "MIT",
"dependencies": {
"css-tree": "^3.0.0"
},
"bin": {
"specificity": "bin/cli.js"
}
},
"node_modules/@csstools/color-helpers": {
"version": "6.0.2",
"resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-6.0.2.tgz",
"integrity": "sha512-LMGQLS9EuADloEFkcTBR3BwV/CGHV7zyDxVRtVDTwdI2Ca4it0CCVTT9wCkxSgokjE5Ho41hEPgb8OEUwoXr6Q==",
"dev": true,
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/csstools"
},
{
"type": "opencollective",
"url": "https://opencollective.com/csstools"
}
],
"license": "MIT-0",
"engines": {
"node": ">=20.19.0"
}
},
"node_modules/@csstools/css-calc": {
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-3.2.1.tgz",
"integrity": "sha512-DtdHlgXh5ZkA43cwBcAm+huzgJiwx3ZTWVjBs94kwz2xKqSimDA3lBgCjphYgwgVUMWatSM0pDd8TILB1yrVVg==",
"dev": true,
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/csstools"
},
{
"type": "opencollective",
"url": "https://opencollective.com/csstools"
}
],
"license": "MIT",
"engines": {
"node": ">=20.19.0"
},
"peerDependencies": {
"@csstools/css-parser-algorithms": "^4.0.0",
"@csstools/css-tokenizer": "^4.0.0"
}
},
"node_modules/@csstools/css-color-parser": {
"version": "4.1.7",
"resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-4.1.7.tgz",
"integrity": "sha512-CmjJFQTFQx/U/xNJhSjCQ0ilpesPmNQ8+eOUeM/+kDOVW33qsIjeOXc27vrQDdWVkf83ZSWwtg7kXSUvKDJ8cQ==",
"dev": true,
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/csstools"
},
{
"type": "opencollective",
"url": "https://opencollective.com/csstools"
}
],
"license": "MIT",
"dependencies": {
"@csstools/color-helpers": "^6.0.2",
"@csstools/css-calc": "^3.2.1"
},
"engines": {
"node": ">=20.19.0"
},
"peerDependencies": {
"@csstools/css-parser-algorithms": "^4.0.0",
"@csstools/css-tokenizer": "^4.0.0"
}
},
"node_modules/@csstools/css-parser-algorithms": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-4.0.0.tgz",
"integrity": "sha512-+B87qS7fIG3L5h3qwJ/IFbjoVoOe/bpOdh9hAjXbvx0o8ImEmUsGXN0inFOnk2ChCFgqkkGFQ+TpM5rbhkKe4w==",
"dev": true,
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/csstools"
},
{
"type": "opencollective",
"url": "https://opencollective.com/csstools"
}
],
"license": "MIT",
"engines": {
"node": ">=20.19.0"
},
"peerDependencies": {
"@csstools/css-tokenizer": "^4.0.0"
}
},
"node_modules/@csstools/css-syntax-patches-for-csstree": {
"version": "1.1.5",
"resolved": "https://registry.npmjs.org/@csstools/css-syntax-patches-for-csstree/-/css-syntax-patches-for-csstree-1.1.5.tgz",
"integrity": "sha512-oNjBvzLq2GPZtJphCjLqXow/cHySHSgtxvKZb7OqSZ/xHgw6NWNhfad+6AB9cLeVm6eA9d/qMll3JdEHjy6M+A==",
"dev": true,
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/csstools"
},
{
"type": "opencollective",
"url": "https://opencollective.com/csstools"
}
],
"license": "MIT-0",
"peerDependencies": {
"css-tree": "^3.2.1"
},
"peerDependenciesMeta": {
"css-tree": {
"optional": true
}
}
},
"node_modules/@csstools/css-tokenizer": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-4.0.0.tgz",
"integrity": "sha512-QxULHAm7cNu72w97JUNCBFODFaXpbDg+dP8b/oWFAZ2MTRppA3U00Y2L1HqaS4J6yBqxwa/Y3nMBaxVKbB/NsA==",
"dev": true,
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/csstools"
},
{
"type": "opencollective",
"url": "https://opencollective.com/csstools"
}
],
"license": "MIT",
"engines": {
"node": ">=20.19.0"
}
},
"node_modules/@emmetio/abbreviation": { "node_modules/@emmetio/abbreviation": {
"version": "2.3.3", "version": "2.3.3",
"resolved": "https://registry.npmjs.org/@emmetio/abbreviation/-/abbreviation-2.3.3.tgz", "resolved": "https://registry.npmjs.org/@emmetio/abbreviation/-/abbreviation-2.3.3.tgz",
@@ -994,6 +1199,24 @@
"node": ">=12" "node": ">=12"
} }
}, },
"node_modules/@exodus/bytes": {
"version": "1.15.1",
"resolved": "https://registry.npmjs.org/@exodus/bytes/-/bytes-1.15.1.tgz",
"integrity": "sha512-S6mL0yNB/Abt9Ei4tq8gDhcczc4S3+vQ4ra7vxnAf+YHC02srtqxKKZghx2Dq6p0e66THKwR6r8N6P95wEty7Q==",
"dev": true,
"license": "MIT",
"engines": {
"node": "^20.19.0 || ^22.12.0 || >=24.0.0"
},
"peerDependencies": {
"@noble/hashes": "^1.8.0 || ^2.0.0"
},
"peerDependenciesMeta": {
"@noble/hashes": {
"optional": true
}
}
},
"node_modules/@img/sharp-darwin-arm64": { "node_modules/@img/sharp-darwin-arm64": {
"version": "0.33.5", "version": "0.33.5",
"resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.33.5.tgz", "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.33.5.tgz",
@@ -2485,6 +2708,16 @@
"node": ">=6.0.0" "node": ">=6.0.0"
} }
}, },
"node_modules/bidi-js": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/bidi-js/-/bidi-js-1.0.3.tgz",
"integrity": "sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw==",
"dev": true,
"license": "MIT",
"dependencies": {
"require-from-string": "^2.0.2"
}
},
"node_modules/binary-extensions": { "node_modules/binary-extensions": {
"version": "2.3.0", "version": "2.3.0",
"resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz",
@@ -3070,6 +3303,20 @@
"node": ">=6.6.0" "node": ">=6.6.0"
} }
}, },
"node_modules/css-tree": {
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/css-tree/-/css-tree-3.2.1.tgz",
"integrity": "sha512-X7sjQzceUhu1u7Y/ylrRZFU2FS6LRiFVp6rKLPg23y3x3c3DOKAwuXGDp+PAGjh6CSnCjYeAul8pcT8bAl+lSA==",
"dev": true,
"license": "MIT",
"dependencies": {
"mdn-data": "2.27.1",
"source-map-js": "^1.2.1"
},
"engines": {
"node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0"
}
},
"node_modules/cssesc": { "node_modules/cssesc": {
"version": "3.0.0", "version": "3.0.0",
"resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz",
@@ -3082,6 +3329,20 @@
"node": ">=4" "node": ">=4"
} }
}, },
"node_modules/data-urls": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/data-urls/-/data-urls-7.0.0.tgz",
"integrity": "sha512-23XHcCF+coGYevirZceTVD7NdJOqVn+49IHyxgszm+JIiHLoB2TkmPtsYkNWT1pvRSGkc35L6NHs0yHkN2SumA==",
"dev": true,
"license": "MIT",
"dependencies": {
"whatwg-mimetype": "^5.0.0",
"whatwg-url": "^16.0.0"
},
"engines": {
"node": "^20.19.0 || ^22.12.0 || >=24.0.0"
}
},
"node_modules/debug": { "node_modules/debug": {
"version": "4.4.3", "version": "4.4.3",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
@@ -3099,6 +3360,13 @@
} }
} }
}, },
"node_modules/decimal.js": {
"version": "10.6.0",
"resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.6.0.tgz",
"integrity": "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==",
"dev": true,
"license": "MIT"
},
"node_modules/decode-named-character-reference": { "node_modules/decode-named-character-reference": {
"version": "1.3.0", "version": "1.3.0",
"resolved": "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.3.0.tgz", "resolved": "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.3.0.tgz",
@@ -4073,6 +4341,19 @@
"url": "https://opencollective.com/unified" "url": "https://opencollective.com/unified"
} }
}, },
"node_modules/html-encoding-sniffer": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-6.0.0.tgz",
"integrity": "sha512-CV9TW3Y3f8/wT0BRFc1/KAVQ3TUHiXmaAb6VW9vtiMFf7SLoMd1PdAc4W3KFOFETBJUb90KatHqlsZMWV+R9Gg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@exodus/bytes": "^1.6.0"
},
"engines": {
"node": "^20.19.0 || ^22.12.0 || >=24.0.0"
}
},
"node_modules/html-escaper": { "node_modules/html-escaper": {
"version": "3.0.3", "version": "3.0.3",
"resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-3.0.3.tgz", "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-3.0.3.tgz",
@@ -4295,6 +4576,13 @@
"url": "https://github.com/sponsors/sindresorhus" "url": "https://github.com/sponsors/sindresorhus"
} }
}, },
"node_modules/is-potential-custom-element-name": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz",
"integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==",
"dev": true,
"license": "MIT"
},
"node_modules/is-promise": { "node_modules/is-promise": {
"version": "4.0.0", "version": "4.0.0",
"resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz",
@@ -4355,6 +4643,83 @@
"js-yaml": "bin/js-yaml.js" "js-yaml": "bin/js-yaml.js"
} }
}, },
"node_modules/jsdom": {
"version": "29.1.1",
"resolved": "https://registry.npmjs.org/jsdom/-/jsdom-29.1.1.tgz",
"integrity": "sha512-ECi4Fi2f7BdJtUKTflYRTiaMxIB0O6zfR1fX0GXpUrf6flp8QIYn1UT20YQqdSOfk2dfkCwS8LAFoJDEppNK5Q==",
"dev": true,
"license": "MIT",
"dependencies": {
"@asamuzakjp/css-color": "^5.1.11",
"@asamuzakjp/dom-selector": "^7.1.1",
"@bramus/specificity": "^2.4.2",
"@csstools/css-syntax-patches-for-csstree": "^1.1.3",
"@exodus/bytes": "^1.15.0",
"css-tree": "^3.2.1",
"data-urls": "^7.0.0",
"decimal.js": "^10.6.0",
"html-encoding-sniffer": "^6.0.0",
"is-potential-custom-element-name": "^1.0.1",
"lru-cache": "^11.3.5",
"parse5": "^8.0.1",
"saxes": "^6.0.0",
"symbol-tree": "^3.2.4",
"tough-cookie": "^6.0.1",
"undici": "^7.25.0",
"w3c-xmlserializer": "^5.0.0",
"webidl-conversions": "^8.0.1",
"whatwg-mimetype": "^5.0.0",
"whatwg-url": "^16.0.1",
"xml-name-validator": "^5.0.0"
},
"engines": {
"node": "^20.19.0 || ^22.13.0 || >=24.0.0"
},
"peerDependencies": {
"canvas": "^3.0.0"
},
"peerDependenciesMeta": {
"canvas": {
"optional": true
}
}
},
"node_modules/jsdom/node_modules/entities": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/entities/-/entities-8.0.0.tgz",
"integrity": "sha512-zwfzJecQ/Uej6tusMqwAqU/6KL2XaB2VZ2Jg54Je6ahNBGNH6Ek6g3jjNCF0fG9EWQKGZNddNjU5F1ZQn/sBnA==",
"dev": true,
"license": "BSD-2-Clause",
"engines": {
"node": ">=20.19.0"
},
"funding": {
"url": "https://github.com/fb55/entities?sponsor=1"
}
},
"node_modules/jsdom/node_modules/lru-cache": {
"version": "11.5.1",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.5.1.tgz",
"integrity": "sha512-RPimw/7aMdv2oqRrxKwvZXcPfwBrn/JZ2xYcY9Hus/6LaS3VOAKVWKWgNLCFSiOm1ESXinjsDlidVU7JlnCN2A==",
"dev": true,
"license": "BlueOak-1.0.0",
"engines": {
"node": "20 || >=22"
}
},
"node_modules/jsdom/node_modules/parse5": {
"version": "8.0.1",
"resolved": "https://registry.npmjs.org/parse5/-/parse5-8.0.1.tgz",
"integrity": "sha512-z1e/HMG90obSGeidlli3hj7cbocou0/wa5HacvI3ASx34PecNjNQeaHNo5WIZpWofN9kgkqV1q5YvXe3F0FoPw==",
"dev": true,
"license": "MIT",
"dependencies": {
"entities": "^8.0.0"
},
"funding": {
"url": "https://github.com/inikulin/parse5?sponsor=1"
}
},
"node_modules/jsesc": { "node_modules/jsesc": {
"version": "3.1.0", "version": "3.1.0",
"resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz",
@@ -4798,6 +5163,13 @@
"url": "https://opencollective.com/unified" "url": "https://opencollective.com/unified"
} }
}, },
"node_modules/mdn-data": {
"version": "2.27.1",
"resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.27.1.tgz",
"integrity": "sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ==",
"dev": true,
"license": "CC0-1.0"
},
"node_modules/media-typer": { "node_modules/media-typer": {
"version": "1.1.0", "version": "1.1.0",
"resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz",
@@ -6158,6 +6530,16 @@
"node": ">= 0.10" "node": ">= 0.10"
} }
}, },
"node_modules/punycode": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
"integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=6"
}
},
"node_modules/qs": { "node_modules/qs": {
"version": "6.15.0", "version": "6.15.0",
"resolved": "https://registry.npmjs.org/qs/-/qs-6.15.0.tgz", "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.0.tgz",
@@ -6669,6 +7051,19 @@
"node": ">=11.0.0" "node": ">=11.0.0"
} }
}, },
"node_modules/saxes": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz",
"integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==",
"dev": true,
"license": "ISC",
"dependencies": {
"xmlchars": "^2.2.0"
},
"engines": {
"node": ">=v12.22.7"
}
},
"node_modules/section-matter": { "node_modules/section-matter": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/section-matter/-/section-matter-1.0.0.tgz", "resolved": "https://registry.npmjs.org/section-matter/-/section-matter-1.0.0.tgz",
@@ -7124,6 +7519,13 @@
"url": "https://github.com/sponsors/ljharb" "url": "https://github.com/sponsors/ljharb"
} }
}, },
"node_modules/symbol-tree": {
"version": "3.2.4",
"resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz",
"integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==",
"dev": true,
"license": "MIT"
},
"node_modules/tailwindcss": { "node_modules/tailwindcss": {
"version": "3.4.19", "version": "3.4.19",
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.19.tgz", "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.19.tgz",
@@ -7216,6 +7618,26 @@
"url": "https://github.com/sponsors/SuperchupuDev" "url": "https://github.com/sponsors/SuperchupuDev"
} }
}, },
"node_modules/tldts": {
"version": "7.4.3",
"resolved": "https://registry.npmjs.org/tldts/-/tldts-7.4.3.tgz",
"integrity": "sha512-A3BDQBeeukYPzB4QdQ1DtdlUmp4x2OCH8n5UVhEWbyANxNep8GavottKzd1xYKFJKjUgMyPT7EzOfnBO55s8Sg==",
"dev": true,
"license": "MIT",
"dependencies": {
"tldts-core": "^7.4.3"
},
"bin": {
"tldts": "bin/cli.js"
}
},
"node_modules/tldts-core": {
"version": "7.4.3",
"resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-7.4.3.tgz",
"integrity": "sha512-27ep5H9PzdBrNd5OFM/j3WCU8F3kPwM9D0BOaOf7uYfxMJfyr0K5Tjj69Gri+sZlh2WXd5buIm47NuPF29CDiw==",
"dev": true,
"license": "MIT"
},
"node_modules/to-regex-range": { "node_modules/to-regex-range": {
"version": "5.0.1", "version": "5.0.1",
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
@@ -7237,6 +7659,32 @@
"node": ">=0.6" "node": ">=0.6"
} }
}, },
"node_modules/tough-cookie": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-6.0.1.tgz",
"integrity": "sha512-LktZQb3IeoUWB9lqR5EWTHgW/VTITCXg4D21M+lvybRVdylLrRMnqaIONLVb5mav8vM19m44HIcGq4qASeu2Qw==",
"dev": true,
"license": "BSD-3-Clause",
"dependencies": {
"tldts": "^7.0.5"
},
"engines": {
"node": ">=16"
}
},
"node_modules/tr46": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/tr46/-/tr46-6.0.0.tgz",
"integrity": "sha512-bLVMLPtstlZ4iMQHpFHTR7GAGj2jxi8Dg0s2h2MafAE4uSWF98FC/3MomU51iQAMf8/qDUbKWf5GxuvvVcXEhw==",
"dev": true,
"license": "MIT",
"dependencies": {
"punycode": "^2.3.1"
},
"engines": {
"node": ">=20"
}
},
"node_modules/trim-lines": { "node_modules/trim-lines": {
"version": "3.0.1", "version": "3.0.1",
"resolved": "https://registry.npmjs.org/trim-lines/-/trim-lines-3.0.1.tgz", "resolved": "https://registry.npmjs.org/trim-lines/-/trim-lines-3.0.1.tgz",
@@ -7347,6 +7795,16 @@
"semver": "^7.3.8" "semver": "^7.3.8"
} }
}, },
"node_modules/undici": {
"version": "7.28.0",
"resolved": "https://registry.npmjs.org/undici/-/undici-7.28.0.tgz",
"integrity": "sha512-cRZYrTDwWznlnRiPjggAGxZXanty6M8RV1ff8Wm4LWXBp7/IG8v5DnOm74DtUBp9OONpK75YlPnIjQqX0dBDtA==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=20.18.1"
}
},
"node_modules/undici-types": { "node_modules/undici-types": {
"version": "7.16.0", "version": "7.16.0",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz",
@@ -7959,6 +8417,19 @@
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/w3c-xmlserializer": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz",
"integrity": "sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==",
"dev": true,
"license": "MIT",
"dependencies": {
"xml-name-validator": "^5.0.0"
},
"engines": {
"node": ">=18"
}
},
"node_modules/web-namespaces": { "node_modules/web-namespaces": {
"version": "2.0.1", "version": "2.0.1",
"resolved": "https://registry.npmjs.org/web-namespaces/-/web-namespaces-2.0.1.tgz", "resolved": "https://registry.npmjs.org/web-namespaces/-/web-namespaces-2.0.1.tgz",
@@ -7969,6 +8440,41 @@
"url": "https://github.com/sponsors/wooorm" "url": "https://github.com/sponsors/wooorm"
} }
}, },
"node_modules/webidl-conversions": {
"version": "8.0.1",
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-8.0.1.tgz",
"integrity": "sha512-BMhLD/Sw+GbJC21C/UgyaZX41nPt8bUTg+jWyDeg7e7YN4xOM05YPSIXceACnXVtqyEw/LMClUQMtMZ+PGGpqQ==",
"dev": true,
"license": "BSD-2-Clause",
"engines": {
"node": ">=20"
}
},
"node_modules/whatwg-mimetype": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-5.0.0.tgz",
"integrity": "sha512-sXcNcHOC51uPGF0P/D4NVtrkjSU2fNsm9iog4ZvZJsL3rjoDAzXZhkm2MWt1y+PUdggKAYVoMAIYcs78wJ51Cw==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=20"
}
},
"node_modules/whatwg-url": {
"version": "16.0.1",
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-16.0.1.tgz",
"integrity": "sha512-1to4zXBxmXHV3IiSSEInrreIlu02vUOvrhxJJH5vcxYTBDAx51cqZiKdyTxlecdKNSjj8EcxGBxNf6Vg+945gw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@exodus/bytes": "^1.11.0",
"tr46": "^6.0.0",
"webidl-conversions": "^8.0.1"
},
"engines": {
"node": "^20.19.0 || ^22.12.0 || >=24.0.0"
}
},
"node_modules/which-pm": { "node_modules/which-pm": {
"version": "3.0.1", "version": "3.0.1",
"resolved": "https://registry.npmjs.org/which-pm/-/which-pm-3.0.1.tgz", "resolved": "https://registry.npmjs.org/which-pm/-/which-pm-3.0.1.tgz",
@@ -8028,6 +8534,23 @@
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
"license": "ISC" "license": "ISC"
}, },
"node_modules/xml-name-validator": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-5.0.0.tgz",
"integrity": "sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==",
"dev": true,
"license": "Apache-2.0",
"engines": {
"node": ">=18"
}
},
"node_modules/xmlchars": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz",
"integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==",
"dev": true,
"license": "MIT"
},
"node_modules/xxhash-wasm": { "node_modules/xxhash-wasm": {
"version": "1.1.0", "version": "1.1.0",
"resolved": "https://registry.npmjs.org/xxhash-wasm/-/xxhash-wasm-1.1.0.tgz", "resolved": "https://registry.npmjs.org/xxhash-wasm/-/xxhash-wasm-1.1.0.tgz",
+1
View File
@@ -49,6 +49,7 @@
"@astrojs/tailwind": "^5.1.0", "@astrojs/tailwind": "^5.1.0",
"@playwright/test": "^1.58.2", "@playwright/test": "^1.58.2",
"@types/nodemailer": "^7.0.11", "@types/nodemailer": "^7.0.11",
"jsdom": "^29.1.1",
"playwright": "^1.58.2", "playwright": "^1.58.2",
"tailwindcss": "^3.4.1", "tailwindcss": "^3.4.1",
"typescript": "^5.9.3" "typescript": "^5.9.3"
+52
View File
@@ -58,6 +58,9 @@ Allow: /
User-agent: GPTBot User-agent: GPTBot
Allow: / Allow: /
User-agent: OAI-SearchBot
Allow: /
# Anthropic/Claude # Anthropic/Claude
User-agent: anthropic-ai User-agent: anthropic-ai
Allow: / Allow: /
@@ -65,10 +68,22 @@ Allow: /
User-agent: Claude-Web User-agent: Claude-Web
Allow: / Allow: /
User-agent: ClaudeBot
Allow: /
User-agent: Claude-SearchBot
Allow: /
User-agent: Claude-User
Allow: /
# Perplexity AI # Perplexity AI
User-agent: PerplexityBot User-agent: PerplexityBot
Allow: / Allow: /
User-agent: Perplexity-User
Allow: /
# Google Gemini/Bard # Google Gemini/Bard
User-agent: Google-Extended User-agent: Google-Extended
Allow: / Allow: /
@@ -80,3 +95,40 @@ Allow: /
# Meta AI # Meta AI
User-agent: FacebookBot User-agent: FacebookBot
Allow: / 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: /
+25
View File
@@ -15,6 +15,31 @@
<changefreq>monthly</changefreq> <changefreq>monthly</changefreq>
<priority>0.9</priority> <priority>0.9</priority>
</url> </url>
<url>
<loc>https://workroot.in/services/custom-erp-development/</loc>
<changefreq>monthly</changefreq>
<priority>0.8</priority>
</url>
<url>
<loc>https://workroot.in/services/university-erp/</loc>
<changefreq>monthly</changefreq>
<priority>0.8</priority>
</url>
<url>
<loc>https://workroot.in/services/government-it-solutions-gem-vendor/</loc>
<changefreq>monthly</changefreq>
<priority>0.8</priority>
</url>
<url>
<loc>https://workroot.in/services/police-management-system/</loc>
<changefreq>monthly</changefreq>
<priority>0.8</priority>
</url>
<url>
<loc>https://workroot.in/services/react-native-app-development/</loc>
<changefreq>monthly</changefreq>
<priority>0.8</priority>
</url>
<url> <url>
<loc>https://workroot.in/portfolio/</loc> <loc>https://workroot.in/portfolio/</loc>
<changefreq>weekly</changefreq> <changefreq>weekly</changefreq>
+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 By subscribing, you agree to our
<a href="/privacy" class="text-primary hover:underline">Privacy Policy</a>. <a href="/privacy" class="text-primary hover:underline">Privacy Policy</a>.
</p> </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> </div>
</div> </div>
@@ -193,6 +210,106 @@ const currentYear = new Date().getFullYear();
</div> </div>
</footer> </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> <script>
import { trackNewsletterSignup } from '../utils/analytics'; import { trackNewsletterSignup } from '../utils/analytics';
+22 -10
View File
@@ -18,56 +18,68 @@
// - Keyboard: native <button> handles Enter & Space; Tab moves between // - Keyboard: native <button> handles Enter & Space; Tab moves between
// items. No custom key handling needed. // 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 = [ const moneyFaqs = [
{
question: 'How much does a project cost? Do you work with fixed budgets?',
answer:
'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?', question: 'How much does a custom ERP cost?',
answer: 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?', question: 'What is your typical timeline?',
answer: 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?', question: 'Do you sign NDAs?',
answer: 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?', question: 'Who owns the source code?',
answer: 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?', question: 'Do you work with government tenders / GeM?',
answer: 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?', question: 'What technologies do you work with?',
answer: 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?', question: 'Do you offer ongoing support after launch?',
answer: 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?', question: 'What is your development process?',
answer: 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?', question: 'Can you work with our existing team?',
answer: 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?', question: 'How do you ensure project security?',
answer: 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.',
}, },
]; ];
+146 -62
View File
@@ -1,20 +1,25 @@
--- ---
// InlineLeadForm — Three-field lead capture rendered inline on the homepage. // InlineLeadForm — Compact lead capture rendered inline on the homepage.
// //
// The goal is to cut the friction step of routing every CTA through /contact. // Goal: let visitors who reach the bottom of the homepage submit a project
// Visitors who reach the bottom of the homepage can submit name + contact // enquiry without the friction of navigating to /contact. The submission
// (email OR phone) + project type without a navigation. The submission reuses // reuses the SAME /api/contact endpoint as the full contact page and is tagged
// /api/contact under the hood and is tagged `source: 'homepage-inline'` so the // `source: 'homepage-inline'` so the backend / analytics layer can segment
// backend / analytics layer can segment these leads cleanly. // these leads cleanly (it also routes them to the `[Project Inquiry]` admin
// subject prefix).
// //
// Why a single "contact" field instead of separate email + phone: // Fields (kept to 5, mobile-first):
// - 3 fields keeps the form psychologically tiny and dramatically lifts // 1. Full name — required
// completion. Real-world conversion testing repeatedly shows 3 → 5+ fields // 2. Email — required, type="email" (email keyboard on mobile)
// is the biggest single-step drop-off on B2B lead forms. // 3. Phone — optional, type="tel" (numeric keypad on mobile)
// - We sniff the value on submit. If it has an "@" we treat it as email. // 4. Project type — required, <select> mapped to the backend `subject`
// Otherwise we treat it as a phone and synthesize a placeholder email to // 5. Message — optional <textarea>; synthesized when left blank so
// satisfy the backend's strict email validation. The phone is also sent // the backend's message>=10 char rule always passes
// through, so the admin email body shows the real reachable value. //
// Only name + email + project type are *required* — phone and message stay
// optional to keep completion friction low on mobile, which is where most of
// these submissions originate. The backend already treats `phone` as optional
// and only validates its format when present.
--- ---
<section <section
@@ -108,42 +113,70 @@
</p> </p>
</div> </div>
<!-- Contact (email or phone) --> <!-- Email + Phone row -->
<div class="grid sm:grid-cols-2 gap-5">
<!-- Email (required) -->
<div> <div>
<label <label
for="inline-lead-contact" for="inline-lead-email"
class="block text-sm font-semibold text-secondary-700 dark:text-secondary-200 mb-1.5" class="block text-sm font-semibold text-secondary-700 dark:text-secondary-200 mb-1.5"
> >
Email or phone <span class="text-red-500" aria-hidden="true">*</span> Email <span class="text-red-500" aria-hidden="true">*</span>
<span class="sr-only">(required)</span> <span class="sr-only">(required)</span>
</label> </label>
<input <input
type="text" type="email"
id="inline-lead-contact" id="inline-lead-email"
name="contact" name="email"
required required
autocomplete="email tel" maxlength="254"
autocomplete="email"
inputmode="email" inputmode="email"
aria-required="true" aria-required="true"
aria-describedby="inline-lead-contact-hint inline-lead-contact-error" aria-describedby="inline-lead-email-error"
placeholder="jane@company.com or +91 95614 17403" placeholder="jane@company.com"
class="block w-full min-h-[48px] px-4 py-3 rounded-xl border border-secondary-200 dark:border-secondary-600 bg-secondary-50 dark:bg-secondary-700 text-secondary-900 dark:text-secondary-100 placeholder-secondary-400 dark:placeholder-secondary-500 text-base focus:outline-none focus:border-primary focus:ring-2 focus:ring-primary/30 transition-colors" class="block w-full min-h-[48px] px-4 py-3 rounded-xl border border-secondary-200 dark:border-secondary-600 bg-secondary-50 dark:bg-secondary-700 text-secondary-900 dark:text-secondary-100 placeholder-secondary-400 dark:placeholder-secondary-500 text-base focus:outline-none focus:border-primary focus:ring-2 focus:ring-primary/30 transition-colors"
/> />
<p <p
id="inline-lead-contact-hint" id="inline-lead-email-error"
class="mt-1.5 text-xs text-secondary-500 dark:text-secondary-400"
>
Drop your best email or phone number — whichever is easiest.
</p>
<p
id="inline-lead-contact-error"
class="mt-1.5 text-xs text-red-600 dark:text-red-400 hidden" class="mt-1.5 text-xs text-red-600 dark:text-red-400 hidden"
role="alert" role="alert"
> >
Please enter a valid email address or phone number. Please enter a valid email address.
</p> </p>
</div> </div>
<!-- Phone (optional) -->
<div>
<label
for="inline-lead-phone"
class="block text-sm font-semibold text-secondary-700 dark:text-secondary-200 mb-1.5"
>
Phone
<span class="font-normal text-secondary-400 dark:text-secondary-500">(optional)</span>
</label>
<input
type="tel"
id="inline-lead-phone"
name="phone"
autocomplete="tel"
inputmode="tel"
pattern="[+()0-9.\s\-]{7,}"
maxlength="32"
aria-describedby="inline-lead-phone-error"
placeholder="+91 95614 17403"
class="block w-full min-h-[48px] px-4 py-3 rounded-xl border border-secondary-200 dark:border-secondary-600 bg-secondary-50 dark:bg-secondary-700 text-secondary-900 dark:text-secondary-100 placeholder-secondary-400 dark:placeholder-secondary-500 text-base focus:outline-none focus:border-primary focus:ring-2 focus:ring-primary/30 transition-colors"
/>
<p
id="inline-lead-phone-error"
class="mt-1.5 text-xs text-red-600 dark:text-red-400 hidden"
role="alert"
>
Please enter a valid phone number, or leave it blank.
</p>
</div>
</div>
<!-- Project type --> <!-- Project type -->
<div> <div>
<label <label
@@ -184,6 +217,33 @@
</p> </p>
</div> </div>
<!-- Message (optional) -->
<div>
<label
for="inline-lead-message"
class="block text-sm font-semibold text-secondary-700 dark:text-secondary-200 mb-1.5"
>
Project details
<span class="font-normal text-secondary-400 dark:text-secondary-500">(optional)</span>
</label>
<textarea
id="inline-lead-message"
name="message"
rows="3"
maxlength="5000"
aria-describedby="inline-lead-message-error"
placeholder="A sentence or two about what you want to build, your timeline, or budget…"
class="block w-full min-h-[96px] px-4 py-3 rounded-xl border border-secondary-200 dark:border-secondary-600 bg-secondary-50 dark:bg-secondary-700 text-secondary-900 dark:text-secondary-100 placeholder-secondary-400 dark:placeholder-secondary-500 text-base focus:outline-none focus:border-primary focus:ring-2 focus:ring-primary/30 transition-colors resize-y"
></textarea>
<p
id="inline-lead-message-error"
class="mt-1.5 text-xs text-red-600 dark:text-red-400 hidden"
role="alert"
>
Please add a little more detail (at least 10 characters), or leave it blank.
</p>
</div>
<!-- General form error (server-side / network) --> <!-- General form error (server-side / network) -->
<div <div
id="inline-lead-form-error" id="inline-lead-form-error"
@@ -267,13 +327,17 @@
if (!form) return; if (!form) return;
var nameInput = document.getElementById('inline-lead-name'); var nameInput = document.getElementById('inline-lead-name');
var contactInput = document.getElementById('inline-lead-contact'); var emailInput = document.getElementById('inline-lead-email');
var phoneInput = document.getElementById('inline-lead-phone');
var projectTypeInput = document.getElementById('inline-lead-project-type'); var projectTypeInput = document.getElementById('inline-lead-project-type');
var messageInput = document.getElementById('inline-lead-message');
var websiteInput = document.getElementById('inline-lead-website'); var websiteInput = document.getElementById('inline-lead-website');
var nameError = document.getElementById('inline-lead-name-error'); var nameError = document.getElementById('inline-lead-name-error');
var contactError = document.getElementById('inline-lead-contact-error'); var emailError = document.getElementById('inline-lead-email-error');
var phoneError = document.getElementById('inline-lead-phone-error');
var projectTypeError = document.getElementById('inline-lead-project-type-error'); var projectTypeError = document.getElementById('inline-lead-project-type-error');
var messageError = document.getElementById('inline-lead-message-error');
var formError = document.getElementById('inline-lead-form-error'); var formError = document.getElementById('inline-lead-form-error');
var submitBtn = document.getElementById('inline-lead-submit'); var submitBtn = document.getElementById('inline-lead-submit');
@@ -283,7 +347,7 @@
var successPanel = document.getElementById('inline-lead-success'); var successPanel = document.getElementById('inline-lead-success');
// Tolerant phone test: allow +, digits, spaces, dashes, parens, dots. // Tolerant phone test: allow +, digits, spaces, dashes, parens, dots.
// We require at least 7 digits so single-digit / accidental presses fail. // Require at least 7 digits so single-digit / accidental presses fail.
var PHONE_REGEX = /^[+]?[()\d\s.\-]{7,}$/; var PHONE_REGEX = /^[+]?[()\d\s.\-]{7,}$/;
var EMAIL_REGEX = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; var EMAIL_REGEX = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
@@ -311,6 +375,10 @@
} }
} }
function phoneIsValid(val) {
return PHONE_REGEX.test(val) && digitsOnly(val).length >= 7;
}
function validate() { function validate() {
var ok = true; var ok = true;
@@ -322,14 +390,21 @@
showError(nameError, false); showError(nameError, false);
} }
var contactVal = (contactInput.value || '').trim(); var emailVal = (emailInput.value || '').trim();
var isEmail = EMAIL_REGEX.test(contactVal); if (!EMAIL_REGEX.test(emailVal)) {
var isPhone = PHONE_REGEX.test(contactVal) && digitsOnly(contactVal).length >= 7; showError(emailError, true);
if (!contactVal || (!isEmail && !isPhone)) {
showError(contactError, true);
ok = false; ok = false;
} else { } else {
showError(contactError, false); showError(emailError, false);
}
// Phone is optional — only validate format when something was typed.
var phoneVal = (phoneInput.value || '').trim();
if (phoneVal && !phoneIsValid(phoneVal)) {
showError(phoneError, true);
ok = false;
} else {
showError(phoneError, false);
} }
var projectVal = projectTypeInput.value || ''; var projectVal = projectTypeInput.value || '';
@@ -340,13 +415,24 @@
showError(projectTypeError, false); showError(projectTypeError, false);
} }
// Message is optional — only enforce the min length when something was typed.
var messageVal = (messageInput.value || '').trim();
if (messageVal && messageVal.length < 10) {
showError(messageError, true);
ok = false;
} else {
showError(messageError, false);
}
return ok; return ok;
} }
// Live-validate on input/change so the error UI doesn't linger after fix. // Live-validate on blur/change so the error UI clears once a field is fixed.
nameInput.addEventListener('blur', validate); nameInput.addEventListener('blur', validate);
contactInput.addEventListener('blur', validate); emailInput.addEventListener('blur', validate);
phoneInput.addEventListener('blur', validate);
projectTypeInput.addEventListener('change', validate); projectTypeInput.addEventListener('change', validate);
messageInput.addEventListener('blur', validate);
// Map the user-friendly project type label to the backend subject code so // Map the user-friendly project type label to the backend subject code so
// the existing /api/contact validator passes. Anything unmapped → 'other'. // the existing /api/contact validator passes. Anything unmapped → 'other'.
@@ -359,41 +445,39 @@
}; };
function buildPayload() { function buildPayload() {
var contactVal = (contactInput.value || '').trim();
var isEmail = EMAIL_REGEX.test(contactVal);
var nameVal = (nameInput.value || '').trim(); var nameVal = (nameInput.value || '').trim();
var emailVal = (emailInput.value || '').trim();
var phoneVal = (phoneInput.value || '').trim();
var projectType = projectTypeInput.value || 'Other'; var projectType = projectTypeInput.value || 'Other';
var subject = SUBJECT_BY_PROJECT_TYPE[projectType] || 'other'; var subject = SUBJECT_BY_PROJECT_TYPE[projectType] || 'other';
var messageVal = (messageInput.value || '').trim();
var email, phone; // The backend requires a message of at least 10 characters. When the
if (isEmail) { // visitor leaves the (optional) details blank, synthesize a concise
email = contactVal; // summary so the submission still goes through and the admin email
phone = undefined; // carries the useful context.
var message;
if (messageVal.length >= 10) {
message = messageVal;
} else { } else {
// Treat as phone. Synthesize a stable placeholder email so the message =
// backend's strict email validator passes — the admin notification 'New project enquiry from the homepage form.\n\n' +
// surfaces the real phone in both Phone: and Message: fields so the
// team always has a real way to reach the lead.
var digits = digitsOnly(contactVal) || 'unknown';
email = 'lead-' + digits + '@homepage.workroot.in';
phone = contactVal;
}
var message =
'New inline lead from the homepage.\n\n' +
'Project type: ' + projectType + '\n' + 'Project type: ' + projectType + '\n' +
'Preferred contact: ' + contactVal; 'Name: ' + nameVal +
(phoneVal ? '\nPhone: ' + phoneVal : '') +
(messageVal ? '\n\nNote: ' + messageVal : '');
}
var payload = { var payload = {
name: nameVal, name: nameVal,
email: email, email: emailVal,
subject: subject, subject: subject,
message: message, message: message,
source: 'homepage-inline', source: 'homepage-inline',
projectType: projectType, projectType: projectType,
website: (websiteInput && websiteInput.value) || '', website: (websiteInput && websiteInput.value) || '',
}; };
if (phone) payload.phone = phone; if (phoneVal) payload.phone = phoneVal;
return payload; return payload;
} }
+2 -2
View File
@@ -40,12 +40,12 @@ interface Props {
// credibility bug this set was created to fix. // credibility bug this set was created to fix.
// //
// - "20+ Projects" — total delivered engagements // - "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 // - "50,000+ Records Managed" — records under management across deployments
// - "8+ Years Avg Experience" — average team experience // - "8+ Years Avg Experience" — average team experience
const defaultItems: ProofItem[] = [ const defaultItems: ProofItem[] = [
{ value: '20+', label: 'Projects' }, { value: '20+', label: 'Projects' },
{ value: '11', label: 'Districts Deployed' }, { value: '11+', label: 'Districts Deployed' },
{ value: '50,000+', label: 'Records Managed' }, { value: '50,000+', label: 'Records Managed' },
{ value: '8+', label: 'Years Avg Experience' }, { value: '8+', label: 'Years Avg Experience' },
]; ];
+207 -16
View File
@@ -9,6 +9,11 @@ interface BreadcrumbItem {
interface Props { interface Props {
type?: 'WebPage' | 'BlogPosting' | 'Article' | 'Service' | 'AboutPage' | 'ContactPage' | 'FAQPage'; type?: 'WebPage' | 'BlogPosting' | 'Article' | 'Service' | 'AboutPage' | 'ContactPage' | 'FAQPage';
// When true this instance emits ONLY the site-wide base schema
// (Organization + LocalBusiness and WebSite). Rendered once from
// BaseLayout's <head> so every route carries the canonical company data,
// independently of whether the page adds its own page-specific <SEO>.
base?: boolean;
breadcrumbs?: BreadcrumbItem[]; breadcrumbs?: BreadcrumbItem[];
article?: { article?: {
author: string; author: string;
@@ -29,11 +34,173 @@ interface Props {
}>; }>;
} }
const { type = 'WebPage', breadcrumbs, article, service, faq } = Astro.props; const { type = 'WebPage', base = false, breadcrumbs, article, service, faq } = Astro.props;
const baseUrl = 'https://workroot.in'; const baseUrl = 'https://workroot.in';
const currentUrl = Astro.url.href; const currentUrl = Astro.url.href;
const orgName = 'WorkRoot IT Solutions LLP';
const orgId = `${baseUrl}/#organization`;
// ---------------------------------------------------------------------------
// Site-wide base schema — Organization + LocalBusiness and WebSite.
//
// Emitted once per page via `<SEO base />` rendered from BaseLayout's <head>,
// so every route (including pages without their own page-specific <SEO>)
// carries WorkRoot's canonical structured data.
//
// `review` / `aggregateRating` are built from the three real, attributable
// client testimonials shown on the homepage (see the `testimonials` array in
// 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 ·
// 50,000+ records · 99.9% uptime) mirror the canonical set in
// ProofNumbers.astro and the /services stats — keep them identical site-wide.
// ---------------------------------------------------------------------------
const testimonialReviews = [
{
author: 'Ajay Pruthi',
body: 'WorkRoot IT Solutions is very professional and responsive. They built PLNR Financing platform with great attention to detail. The Angular + Spring Boot stack works flawlessly and their ongoing support is excellent.',
},
{
author: 'Dhirendra Singh',
body: 'The FPC Cart e-commerce platform delivered by WorkRoot exceeded our expectations. Built with Astro and Golang, it loads incredibly fast and handles our product catalog seamlessly. Their team understood our business needs perfectly.',
},
{
author: 'Dilip Raj',
body: 'WorkRoot delivered our University Management System for IIT Patna on time and within budget. The Next.js + Spring Boot architecture is robust and scalable. They are a reliable technology partner for complex enterprise projects.',
},
];
const reviewNodes = testimonialReviews.map((review) => ({
"@type": "Review",
"author": {
"@type": "Person",
"name": review.author
},
"reviewRating": {
"@type": "Rating",
"ratingValue": "5",
"bestRating": "5",
"worstRating": "1"
},
"reviewBody": review.body
}));
const organizationSchema = {
"@context": "https://schema.org",
"@type": ["Organization", "LocalBusiness"],
"@id": orgId,
"name": orgName,
"alternateName": ["WorkRoot", "WorkRoot IT Solutions"],
"url": baseUrl,
"logo": {
"@type": "ImageObject",
"url": `${baseUrl}/logo.png`,
"width": "512",
"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.",
"foundingDate": "2022",
"founder": {
"@type": "Person",
"name": "Ajay Ghanwat"
},
"address": {
"@type": "PostalAddress",
"streetAddress": "At Post Rajapur Shantinagar, Taluka Khatav",
"addressLocality": "Satara",
"addressRegion": "MH",
"postalCode": "415503",
"addressCountry": "IN"
},
"geo": {
"@type": "GeoCoordinates",
"latitude": "17.7733632",
"longitude": "74.3309312"
},
"hasMap": "https://maps.app.goo.gl/9bZ5ez1pjbU74ATq5",
"telephone": "+91-9561417403",
"email": "admin@workroot.in",
"contactPoint": [{
"@type": "ContactPoint",
"telephone": "+91-9561417403",
"contactType": "customer service",
"email": "admin@workroot.in",
"availableLanguage": ["English", "Hindi", "Marathi"],
"areaServed": "IN"
}, {
"@type": "ContactPoint",
"telephone": "+91-9561417403",
"contactType": "sales",
"email": "admin@workroot.in",
"availableLanguage": ["English", "Hindi", "Marathi"],
"areaServed": "IN"
}],
"openingHoursSpecification": [
{
"@type": "OpeningHoursSpecification",
"dayOfWeek": ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday"],
"opens": "10:00",
"closes": "18:00"
}
],
"priceRange": "$$",
"currenciesAccepted": "INR",
"paymentAccepted": "Cash, Credit Card, Bank Transfer",
"sameAs": [
"https://x.com/GhanwatAjay",
"https://www.linkedin.com/company/workroot",
"https://git.workroot.in/WorkRoot/",
"https://www.facebook.com/profile.php?id=61576503897897"
],
"areaServed": [
{
"@type": "AdministrativeArea",
"name": "Bihar"
},
{
"@type": "Country",
"name": "India"
}
],
"serviceType": ["Web Development", "Mobile App Development", "ERP & Enterprise Systems", "Government IT Solutions"],
"knowsAbout": ["Web Development", "Software Engineering", "Cloud Computing", "Artificial Intelligence", "Machine Learning", "Mobile Development"],
"slogan": "Transforming Ideas Into Digital Reality",
"aggregateRating": {
"@type": "AggregateRating",
"ratingValue": "5.0",
"reviewCount": String(reviewNodes.length),
"bestRating": "5",
"worstRating": "1"
},
"review": reviewNodes
};
const websiteSchema = {
"@context": "https://schema.org",
"@type": "WebSite",
"name": orgName,
"url": baseUrl,
"description": "Professional IT services including web development, cloud solutions, and digital transformation for businesses.",
"publisher": {
"@type": "Organization",
"@id": orgId,
"name": orgName
},
"potentialAction": {
"@type": "SearchAction",
"target": {
"@type": "EntryPoint",
"urlTemplate": "https://workroot.in/blog?search={search_term_string}"
},
"query-input": "required name=search_term_string"
},
"inLanguage": "en-US"
};
// Get page title and description from parent layout props // Get page title and description from parent layout props
const pageTitle = (Astro.props as any).title || 'WorkRoot IT Solutions'; const pageTitle = (Astro.props as any).title || 'WorkRoot IT Solutions';
const pageDescription = (Astro.props as any).description || 'Professional IT services including web development, cloud solutions, and digital transformation for businesses.'; const pageDescription = (Astro.props as any).description || 'Professional IT services including web development, cloud solutions, and digital transformation for businesses.';
@@ -71,7 +238,7 @@ const blogPostingSchema = article && type === 'BlogPosting' ? {
"dateModified": article.dateModified || article.datePublished, "dateModified": article.dateModified || article.datePublished,
"publisher": { "publisher": {
"@type": "Organization", "@type": "Organization",
"name": "WorkRoot IT Solutions", "name": "WorkRoot IT Solutions LLP",
"url": baseUrl, "url": baseUrl,
"logo": { "logo": {
"@type": "ImageObject", "@type": "ImageObject",
@@ -90,7 +257,9 @@ const blogPostingSchema = article && type === 'BlogPosting' ? {
"inLanguage": "en-US" "inLanguage": "en-US"
} : null; } : null;
// Generate Service schema // Generate Service schema — reusable node; each service page passes its own
// name/description via the `service` prop. Provider is linked by @id to the
// site-wide Organization node so the structured-data graph stays connected.
const serviceSchema = service ? { const serviceSchema = service ? {
"@context": "https://schema.org", "@context": "https://schema.org",
"@type": "Service", "@type": "Service",
@@ -98,10 +267,20 @@ const serviceSchema = service ? {
"description": service.description, "description": service.description,
"provider": { "provider": {
"@type": "Organization", "@type": "Organization",
"name": "WorkRoot IT Solutions", "@id": orgId,
"name": orgName,
"url": baseUrl "url": baseUrl
}, },
"areaServed": "Worldwide", "areaServed": [
{
"@type": "AdministrativeArea",
"name": "Bihar"
},
{
"@type": "Country",
"name": "India"
}
],
"serviceType": service.name "serviceType": service.name
} : null; } : null;
@@ -119,7 +298,7 @@ const webPageSchema = type === 'WebPage' && Astro.url.pathname !== '/' ? {
}, },
"about": { "about": {
"@type": "Organization", "@type": "Organization",
"name": "WorkRoot IT Solutions" "name": "WorkRoot IT Solutions LLP"
}, },
"primaryImageOfPage": { "primaryImageOfPage": {
"@type": "ImageObject", "@type": "ImageObject",
@@ -151,7 +330,7 @@ const aboutPageSchema = type === 'AboutPage' ? {
"inLanguage": "en-US", "inLanguage": "en-US",
"mainEntity": { "mainEntity": {
"@type": "Organization", "@type": "Organization",
"name": "WorkRoot IT Solutions", "name": "WorkRoot IT Solutions LLP",
"url": baseUrl "url": baseUrl
} }
} : null; } : null;
@@ -166,8 +345,8 @@ const contactPageSchema = type === 'ContactPage' ? {
"inLanguage": "en-US", "inLanguage": "en-US",
"mainEntity": { "mainEntity": {
"@type": "LocalBusiness", "@type": "LocalBusiness",
"name": "WorkRoot IT Solutions", "name": "WorkRoot IT Solutions LLP",
"alternateName": "WorkRoot IT Solutions LLP", "alternateName": "WorkRoot IT Solutions",
"url": baseUrl, "url": baseUrl,
"telephone": "+91-9561417403", "telephone": "+91-9561417403",
"email": "admin@workroot.in", "email": "admin@workroot.in",
@@ -214,30 +393,42 @@ const contactPageSchema = type === 'ContactPage' ? {
} : null; } : null;
--- ---
{breadcrumbSchema && ( {/* Site-wide base schema — emitted only by the single `<SEO base />` rendered
from BaseLayout's <head>, so it appears exactly once on every page. */}
{base && (
<script type="application/ld+json" set:html={JSON.stringify(organizationSchema)} />
)}
{base && (
<script type="application/ld+json" set:html={JSON.stringify(websiteSchema)} />
)}
{/* Page-specific schema — never emitted by the base instance, so there is no
duplication with the site-wide Organization/WebSite nodes above. */}
{!base && breadcrumbSchema && (
<script type="application/ld+json" set:html={JSON.stringify(breadcrumbSchema)} /> <script type="application/ld+json" set:html={JSON.stringify(breadcrumbSchema)} />
)} )}
{blogPostingSchema && ( {!base && blogPostingSchema && (
<script type="application/ld+json" set:html={JSON.stringify(blogPostingSchema)} /> <script type="application/ld+json" set:html={JSON.stringify(blogPostingSchema)} />
)} )}
{serviceSchema && ( {!base && serviceSchema && (
<script type="application/ld+json" set:html={JSON.stringify(serviceSchema)} /> <script type="application/ld+json" set:html={JSON.stringify(serviceSchema)} />
)} )}
{webPageSchema && ( {!base && webPageSchema && (
<script type="application/ld+json" set:html={JSON.stringify(webPageSchema)} /> <script type="application/ld+json" set:html={JSON.stringify(webPageSchema)} />
)} )}
{faqPageSchema && ( {!base && faqPageSchema && (
<script type="application/ld+json" set:html={JSON.stringify(faqPageSchema)} /> <script type="application/ld+json" set:html={JSON.stringify(faqPageSchema)} />
)} )}
{aboutPageSchema && ( {!base && aboutPageSchema && (
<script type="application/ld+json" set:html={JSON.stringify(aboutPageSchema)} /> <script type="application/ld+json" set:html={JSON.stringify(aboutPageSchema)} />
)} )}
{contactPageSchema && ( {!base && contactPageSchema && (
<script type="application/ld+json" set:html={JSON.stringify(contactPageSchema)} /> <script type="application/ld+json" set:html={JSON.stringify(contactPageSchema)} />
)} )}
+160
View File
@@ -0,0 +1,160 @@
---
// ServiceFaq — reusable, prop-driven FAQ accordion for the keyword-focused
// service landing pages (e.g. /services/custom-erp-development).
//
// IMPORTANT: this component renders only the VISIBLE accordion. It does NOT
// emit FAQPage JSON-LD. The owning page is expected to pass the same `faqs`
// array into <SEO faq={faqs} />, which emits exactly one FAQPage node. Keeping
// schema emission in SEO.astro (and out of here) guarantees a single FAQPage
// per page even when several of these accordions could theoretically render.
//
// Visual + a11y behaviour mirrors HomepageFaq.astro (single-open accordion,
// real <button> questions, aria-expanded, grid-rows expand with no layout
// shift) but uses a `svc-faq-*` class namespace so it never collides with the
// homepage FAQ script if both ever appear together.
interface FaqItem {
question: string;
answer: string;
}
interface Props {
faqs: FaqItem[];
eyebrow?: string;
heading?: string;
subheading?: string;
}
const {
faqs,
eyebrow = 'Before you reach out',
heading = 'Frequently asked questions',
subheading = 'Straight answers on scope, cost, timelines, ownership, and how we work.',
} = Astro.props;
---
<section
class="py-20 sm:py-24 bg-secondary-50 dark:bg-secondary-950 border-t border-secondary-100 dark:border-secondary-800"
aria-labelledby="svc-faq-heading"
>
<div class="container-wrapper">
<div class="max-w-3xl mx-auto">
<div class="text-center mb-12 sm:mb-16" data-animate="fade-up">
<span
class="inline-block px-4 py-2 rounded-full bg-accent-50 dark:bg-accent-900/30 text-accent-700 dark:text-accent-300 text-xs sm:text-sm font-bold uppercase tracking-wider mb-4"
>
{eyebrow}
</span>
<h2
id="svc-faq-heading"
class="text-3xl sm:text-4xl lg:text-5xl font-bold text-secondary-900 dark:text-secondary-100 mb-4 leading-tight"
>
{heading}
</h2>
<p class="text-base sm:text-lg text-secondary-600 dark:text-secondary-300">
{subheading}
</p>
</div>
<ul class="space-y-3 sm:space-y-4" role="list">
{
faqs.map((item, index) => (
<li
class="bg-white dark:bg-secondary-800 rounded-2xl border border-secondary-200 dark:border-secondary-700 hover:border-primary-300 dark:hover:border-primary-500/60 shadow-sm transition-colors duration-200 overflow-hidden"
>
<button
type="button"
class="svc-faq-question w-full text-left px-5 sm:px-7 py-5 sm:py-6 flex items-start justify-between gap-4 focus:outline-none focus-visible:ring-2 focus-visible:ring-primary-400 focus-visible:ring-offset-2 focus-visible:ring-offset-secondary-50 dark:focus-visible:ring-offset-secondary-950 rounded-2xl"
aria-expanded="false"
aria-controls={`svc-faq-answer-${index}`}
id={`svc-faq-question-${index}`}
>
<span class="font-semibold text-base sm:text-lg text-secondary-900 dark:text-secondary-100 leading-snug">
{item.question}
</span>
<svg
class="svc-faq-icon w-5 h-5 sm:w-6 sm:h-6 mt-0.5 sm:mt-1 text-primary-500 dark:text-primary-400 flex-shrink-0 transition-transform duration-300"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
aria-hidden="true"
>
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2.5" d="M19 9l-7 7-7-7" />
</svg>
</button>
<div
class="svc-faq-answer grid grid-rows-[0fr] transition-[grid-template-rows] duration-300 ease-out"
id={`svc-faq-answer-${index}`}
role="region"
aria-labelledby={`svc-faq-question-${index}`}
>
<div class="overflow-hidden">
<p class="px-5 sm:px-7 pb-5 sm:pb-6 -mt-1 text-sm sm:text-base text-secondary-600 dark:text-secondary-300 leading-relaxed">
{item.answer}
</p>
</div>
</div>
</li>
))
}
</ul>
<p class="text-center text-sm sm:text-base text-secondary-600 dark:text-secondary-400 mt-10 sm:mt-12">
Have a different question?{' '}
<a
href="#inline-lead-form"
class="font-semibold text-primary-600 dark:text-primary-300 hover:underline"
>
Ask us directly &rarr;
</a>
</p>
</div>
</div>
</section>
<style>
.svc-faq-answer[data-expanded='true'] {
grid-template-rows: 1fr;
}
.svc-faq-question[aria-expanded='true'] .svc-faq-icon {
transform: rotate(180deg);
}
@media (prefers-reduced-motion: reduce) {
.svc-faq-answer,
.svc-faq-icon {
transition: none !important;
}
}
</style>
<script is:inline>
(function () {
var buttons = document.querySelectorAll('.svc-faq-question');
if (!buttons.length) return;
function setExpanded(button, expanded) {
button.setAttribute('aria-expanded', expanded ? 'true' : 'false');
var answerId = button.getAttribute('aria-controls');
if (!answerId) return;
var answer = document.getElementById(answerId);
if (!answer) return;
if (expanded) {
answer.setAttribute('data-expanded', 'true');
} else {
answer.removeAttribute('data-expanded');
}
}
buttons.forEach(function (button) {
button.addEventListener('click', function () {
var isOpen = button.getAttribute('aria-expanded') === 'true';
buttons.forEach(function (other) {
if (other !== button) setExpanded(other, false);
});
setExpanded(button, !isOpen);
});
});
})();
</script>
@@ -1,6 +1,8 @@
--- ---
title: "How AI is Transforming Modern Business Operations" title: "How AI is Transforming Modern Business Operations"
description: "Explore the practical applications of artificial intelligence in streamlining business processes and driving innovation." description: "Explore the practical applications of artificial intelligence in streamlining business processes and driving innovation."
seoTitle: "How AI Is Transforming Business Operations"
seoDescription: "How AI is reshaping business operations — practical use cases in customer service, predictive analytics, process automation, and intelligent document handling."
pubDate: 2024-01-10 pubDate: 2024-01-10
heroImage: "/images/blog/ai-business.jpg" heroImage: "/images/blog/ai-business.jpg"
category: "AI/ML" category: "AI/ML"
@@ -1,6 +1,8 @@
--- ---
title: "Building Scalable Government IT Systems: Lessons from 11-District Deployments" title: "Building Scalable Government IT Systems: Lessons from 11-District Deployments"
description: "Practical insights from building and deploying large-scale government systems across multiple districts, including Police Management Systems and SCMS projects." description: "Practical insights from building and deploying large-scale government systems across multiple districts, including Police Management Systems and SCMS projects."
seoTitle: "Scaling Government IT Across 11 Districts"
seoDescription: "Lessons from deploying government IT systems across 11 districts — database-per-district architecture, offline-first modules, and audit logging baked in."
pubDate: 2025-12-15 pubDate: 2025-12-15
heroImage: "/images/blog/govt-systems.jpg" heroImage: "/images/blog/govt-systems.jpg"
category: "Cloud" category: "Cloud"
@@ -1,6 +1,8 @@
--- ---
title: "Building Scalable ERP Systems: Lessons from Government & Enterprise Projects" title: "Building Scalable ERP Systems: Lessons from Government & Enterprise Projects"
description: "Practical insights on designing and deploying large-scale ERP and management systems for government bodies and enterprises, drawn from real-world project experience." description: "Practical insights on designing and deploying large-scale ERP and management systems for government bodies and enterprises, drawn from real-world project experience."
seoTitle: "Building Scalable ERP Systems That Last"
seoDescription: "How we design and deploy scalable ERP systems for government and enterprise — modular architecture, role-based access control, and offline-resilient patterns."
pubDate: 2024-02-20 pubDate: 2024-02-20
heroImage: "/images/blog/erp-systems.jpg" heroImage: "/images/blog/erp-systems.jpg"
category: "Web Development" category: "Web Development"
@@ -1,6 +1,8 @@
--- ---
title: "The Complete Guide to Cloud Migration in 2024" title: "The Complete Guide to Cloud Migration in 2024"
description: "A step-by-step approach to migrating your infrastructure to the cloud while minimizing downtime and maximizing ROI." description: "A step-by-step approach to migrating your infrastructure to the cloud while minimizing downtime and maximizing ROI."
seoTitle: "A Complete Guide to Cloud Migration in 2024"
seoDescription: "A step-by-step cloud migration guide for 2024 — assess workloads, containerize apps, adopt infrastructure as code, and cut downtime while maximizing your ROI."
pubDate: 2024-01-05 pubDate: 2024-01-05
heroImage: "/images/blog/cloud-migration.jpg" heroImage: "/images/blog/cloud-migration.jpg"
category: "Cloud" category: "Cloud"
@@ -1,6 +1,8 @@
--- ---
title: "How We Deployed a Police Management System Across 11 Bihar Districts" title: "How We Deployed a Police Management System Across 11 Bihar Districts"
description: "A field-tested account of rolling out a Police Management System to 15,000+ officers across 11 districts in Bihar — the architecture, the rollout plan, the failures, and the numbers that came out the other side." description: "A field-tested account of rolling out a Police Management System to 15,000+ officers across 11 districts in Bihar — the architecture, the rollout plan, the failures, and the numbers that came out the other side."
seoTitle: "Deploying a Police System Across 11 Districts"
seoDescription: "A field account of rolling out a Police Management System to 15,000+ officers across 11 Bihar districts — the architecture, rollout plan, failures, and results."
pubDate: 2026-06-10 pubDate: 2026-06-10
heroImage: "/images/blog/govt-systems.jpg" heroImage: "/images/blog/govt-systems.jpg"
category: "DevOps" category: "DevOps"
@@ -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).
@@ -1,6 +1,8 @@
--- ---
title: "Getting Started with Astro: The Modern Static Site Generator" title: "Getting Started with Astro: The Modern Static Site Generator"
description: "Learn how Astro combines the best of static site generation with modern component frameworks for blazing-fast websites." description: "Learn how Astro combines the best of static site generation with modern component frameworks for blazing-fast websites."
seoTitle: "Getting Started with Astro for Fast Sites"
seoDescription: "Get started with Astro, the modern site generator — learn islands architecture, type-safe content collections, and how it ships fast, low-JavaScript sites."
pubDate: 2024-01-15 pubDate: 2024-01-15
heroImage: "/images/blog/astro-intro.jpg" heroImage: "/images/blog/astro-intro.jpg"
category: "Web Development" category: "Web Development"
@@ -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."
+7
View File
@@ -5,6 +5,13 @@ const blog = defineCollection({
schema: z.object({ schema: z.object({
title: z.string(), title: z.string(),
description: z.string(), description: z.string(),
// Optional SEO overrides. When set they drive the <title> tag and
// <meta name="description"> without changing the on-page article
// heading/excerpt, letting us keep descriptive H1s while tuning the
// search snippet to the 5060 / 150160 character sweet spots.
// `seoTitle` is the text BEFORE the " | WorkRoot" brand suffix.
seoTitle: z.string().optional(),
seoDescription: z.string().optional(),
pubDate: z.coerce.date(), pubDate: z.coerce.date(),
updatedDate: z.coerce.date().optional(), updatedDate: z.coerce.date().optional(),
heroImage: z.string().optional(), heroImage: z.string().optional(),
+28 -106
View File
@@ -4,6 +4,7 @@
import Header from '../components/Header.astro'; import Header from '../components/Header.astro';
import Footer from '../components/Footer.astro'; import Footer from '../components/Footer.astro';
import SEO from '../components/SEO.astro';
import Analytics from '../components/Analytics.astro'; import Analytics from '../components/Analytics.astro';
import PWAInstallPrompt from '../components/PWAInstallPrompt.astro'; import PWAInstallPrompt from '../components/PWAInstallPrompt.astro';
import WhatsAppFab from '../components/WhatsAppFab.astro'; import WhatsAppFab from '../components/WhatsAppFab.astro';
@@ -26,8 +27,15 @@ const {
noIndex = false, noIndex = false,
} = Astro.props; } = Astro.props;
// Full brand name — used for og:site_name and structured data.
const siteName = 'WorkRoot IT Solutions LLP'; const siteName = 'WorkRoot IT Solutions LLP';
const fullTitle = title === 'Home' ? siteName : `${title} | ${siteName}`; // Short brand suffix for the <title> tag so titles stay within the
// 5060 character SEO sweet spot (the full LLP name eats 25+ chars).
const brand = 'WorkRoot';
// The homepage gets a dedicated, query-focused title instead of the bare
// brand name. Pages pass `title="Home"` to opt into it.
const homeTitle = 'Custom Software & Government IT Development | WorkRoot';
const fullTitle = title === 'Home' ? homeTitle : `${title} | ${brand}`;
--- ---
<!doctype html> <!doctype html>
@@ -103,111 +111,14 @@ const fullTitle = title === 'Home' ? siteName : `${title} | ${siteName}`;
<meta name="theme-color" media="(prefers-color-scheme: light)" content="#0891b2" /> <meta name="theme-color" media="(prefers-color-scheme: light)" content="#0891b2" />
<meta name="theme-color" media="(prefers-color-scheme: dark)" content="#0f172a" /> <meta name="theme-color" media="(prefers-color-scheme: dark)" content="#0f172a" />
<!-- JSON-LD Structured Data - Organization + LocalBusiness Schema --> <!-- JSON-LD Structured Data — site-wide base schema (Organization +
<script type="application/ld+json" set:html={JSON.stringify({ LocalBusiness and WebSite). Centralised in SEO.astro and emitted here
"@context": "https://schema.org", in `base` mode so it appears exactly once on EVERY route, including
"@type": ["Organization", "LocalBusiness"], pages that don't add their own page-specific <SEO> (404, privacy,
"name": "WorkRoot IT Solutions LLP", terms, cookies, offline). Page-specific schema (Service, FAQ,
"alternateName": ["WorkRoot", "WorkRoot IT Solutions"], BlogPosting, breadcrumbs, …) is opt-in via the slotted <SEO> each
"url": "https://workroot.in", page renders into the head. -->
"logo": { <SEO base />
"@type": "ImageObject",
"url": "https://workroot.in/logo.png",
"width": "512",
"height": "512"
},
"image": "https://workroot.in/og-image.jpg",
"description": "Professional IT services including web development, cloud solutions, and digital transformation for businesses.",
"foundingDate": "2022",
"founder": {
"@type": "Person",
"name": "Ajay Ghanwat"
},
"address": {
"@type": "PostalAddress",
"streetAddress": "At Post Rajapur Shantinagar, Taluka Khatav",
"addressLocality": "Satara",
"addressRegion": "MH",
"postalCode": "415503",
"addressCountry": "IN"
},
"geo": {
"@type": "GeoCoordinates",
"latitude": "17.7733632",
"longitude": "74.3309312"
},
"hasMap": "https://maps.app.goo.gl/9bZ5ez1pjbU74ATq5",
"telephone": "+91-9561417403",
"email": "admin@workroot.in",
"contactPoint": [{
"@type": "ContactPoint",
"telephone": "+91-9561417403",
"contactType": "customer service",
"email": "admin@workroot.in",
"availableLanguage": ["English", "Hindi", "Marathi"],
"areaServed": "Worldwide"
}, {
"@type": "ContactPoint",
"telephone": "+91-9561417403",
"contactType": "sales",
"email": "admin@workroot.in",
"availableLanguage": ["English", "Hindi", "Marathi"],
"areaServed": "Worldwide"
}],
"openingHoursSpecification": [
{
"@type": "OpeningHoursSpecification",
"dayOfWeek": ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday"],
"opens": "10:00",
"closes": "18:00"
}
],
"priceRange": "$$",
"currenciesAccepted": "INR",
"paymentAccepted": "Cash, Credit Card, Bank Transfer",
"sameAs": [
"https://x.com/GhanwatAjay",
"https://www.linkedin.com/company/workroot",
"https://git.workroot.in/WorkRoot/",
"https://www.facebook.com/profile.php?id=61576503897897"
],
"areaServed": {
"@type": "GeoShape",
"name": "Worldwide"
},
"serviceType": ["Web Development", "Mobile App Development", "AI & Machine Learning", "Cloud Services"],
"knowsAbout": ["Web Development", "Software Engineering", "Cloud Computing", "Artificial Intelligence", "Machine Learning", "Mobile Development"],
"slogan": "Transforming Ideas Into Digital Reality",
"aggregateRating": {
"@type": "AggregateRating",
"ratingValue": "5.0",
"reviewCount": "20",
"bestRating": "5",
"worstRating": "1"
}
})} />
<!-- JSON-LD Structured Data - WebSite Schema -->
<script type="application/ld+json" set:html={JSON.stringify({
"@context": "https://schema.org",
"@type": "WebSite",
"name": "WorkRoot IT Solutions LLP",
"url": "https://workroot.in",
"description": "Professional IT services including web development, cloud solutions, and digital transformation for businesses.",
"publisher": {
"@type": "Organization",
"name": "WorkRoot IT Solutions LLP"
},
"potentialAction": {
"@type": "SearchAction",
"target": {
"@type": "EntryPoint",
"urlTemplate": "https://workroot.in/blog?search={search_term_string}"
},
"query-input": "required name=search_term_string"
},
"inLanguage": "en-US"
})} />
<!-- Preconnect to Google Fonts (must come before font stylesheet) --> <!-- Preconnect to Google Fonts (must come before font stylesheet) -->
<link rel="preconnect" href="https://fonts.googleapis.com" /> <link rel="preconnect" href="https://fonts.googleapis.com" />
@@ -270,6 +181,17 @@ const fullTitle = title === 'Home' ? siteName : `${title} | ${siteName}`;
<!-- Analytics (GA4 or Plausible) --> <!-- Analytics (GA4 or Plausible) -->
<Analytics /> <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 --> <!-- Additional head content slot -->
<slot name="head" /> <slot name="head" />
</head> </head>
+6 -4
View File
@@ -155,11 +155,13 @@ export const onRequest = defineMiddleware(async (context, next) => {
// - inline scripts/styles: Required for Astro framework // - inline scripts/styles: Required for Astro framework
// - https://www.googletagmanager.com & https://www.google-analytics.com: GA4 // - https://www.googletagmanager.com & https://www.google-analytics.com: GA4
// - https://plausible.io: Plausible Analytics // - 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) // Note: unsafe-eval is removed in production (only needed during Astro HMR dev mode)
const isProduction = import.meta.env.PROD; const isProduction = import.meta.env.PROD;
const scriptSrc = isProduction 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' 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"; : "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 = [ const csp = [
"default-src 'self'", "default-src 'self'",
@@ -167,11 +169,11 @@ export const onRequest = defineMiddleware(async (context, next) => {
"style-src 'self' 'unsafe-inline' https://fonts.googleapis.com", "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", "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", "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'", "worker-src 'self'",
"manifest-src 'self'", "manifest-src 'self'",
"object-src 'none'", // Block Flash/plugins (defense-in-depth) "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'", "frame-ancestors 'none'",
"base-uri 'self'", "base-uri 'self'",
"form-action 'self'", "form-action 'self'",
+2 -2
View File
@@ -6,8 +6,8 @@ export const prerender = false;
--- ---
<BaseLayout <BaseLayout
title="Page Not Found" title="Page Not Found — Let's Get You Back on Track"
description="The page you're looking for doesn't exist. Return to WorkRoot IT Solutions homepage." description="The page you're looking for doesn't exist or has moved. Head back to the WorkRoot IT Solutions homepage, or explore our services, portfolio, and blog instead."
noIndex={true} noIndex={true}
> >
<section class="min-h-[70vh] flex items-center justify-center py-20 px-4"> <section class="min-h-[70vh] flex items-center justify-center py-20 px-4">
+3 -3
View File
@@ -49,7 +49,7 @@ const teamMembers = [
// stats counters) so the numbers never contradict across pages. // stats counters) so the numbers never contradict across pages.
const stats = [ const stats = [
{ value: '20+', label: 'Projects' }, { value: '20+', label: 'Projects' },
{ value: '11', label: 'Districts Deployed' }, { value: '11+', label: 'Districts Deployed' },
{ value: '50,000+', label: 'Records Managed' }, { value: '50,000+', label: 'Records Managed' },
{ value: '8+', label: 'Years Avg Experience' }, { value: '8+', label: 'Years Avg Experience' },
]; ];
@@ -114,8 +114,8 @@ const howWeWork = [
--- ---
<BaseLayout <BaseLayout
title="About Us — Our Story & Team" title="About Our Software Engineering Team & Story"
description="WorkRoot IT Solutions LLP: founded 2022, senior in-house engineering team with 8+ years average experience, 20+ projects delivered. Meet the technologists behind your digital transformation. Innovation, integrity, excellence." description="Meet WorkRoot IT Solutions: founded 2022, a senior in-house engineering team averaging 8+ years' experience across 20+ web, mobile, ERP, and govtech projects."
> >
<SEO <SEO
slot="head" slot="head"
+10 -4
View File
@@ -32,21 +32,27 @@ function formatDate(date: Date): string {
} }
const readingTime = getReadingTime(post.body); const readingTime = getReadingTime(post.body);
// SEO meta: prefer per-post overrides so the <title>/description land in the
// 5060 / 150160 character ranges, while the on-page heading below keeps the
// full, descriptive post title. `seoTitle` gets the " | WorkRoot" suffix from
// BaseLayout, so it should be the text before that suffix.
const metaTitle = post.data.seoTitle ?? post.data.title;
const metaDescription = post.data.seoDescription ?? post.data.description;
const shareUrl = encodeURIComponent(Astro.url.href); const shareUrl = encodeURIComponent(Astro.url.href);
const shareTitle = encodeURIComponent(post.data.title); const shareTitle = encodeURIComponent(post.data.title);
const shareDescription = encodeURIComponent(post.data.description); const shareDescription = encodeURIComponent(post.data.description);
--- ---
<BaseLayout <BaseLayout
title={post.data.title} title={metaTitle}
description={post.data.description} description={metaDescription}
ogImage={post.data.heroImage} ogImage={post.data.heroImage}
> >
<SEO <SEO
slot="head" slot="head"
type="BlogPosting" type="BlogPosting"
title={post.data.title} title={metaTitle}
description={post.data.description} description={metaDescription}
breadcrumbs={[ breadcrumbs={[
{ name: 'Home', url: '/' }, { name: 'Home', url: '/' },
{ name: 'Blog', url: '/blog' }, { name: 'Blog', url: '/blog' },
+2 -2
View File
@@ -24,8 +24,8 @@ function formatDate(date: Date): string {
--- ---
<BaseLayout <BaseLayout
title="Blog" title="Software Development & Technology Insights"
description="Insights and articles on web development, AI, cloud computing, and digital transformation from the WorkRoot team." description="Insights on web development, ERP, cloud migration, AI, and government IT from the WorkRoot engineering team — field notes from real, large-scale deployments."
> >
<SEO <SEO
slot="head" slot="head"
+2 -2
View File
@@ -100,8 +100,8 @@ const budgetOptions = ['< ₹1L', '₹1L ₹5L', '₹5L ₹15L', '₹15L
--- ---
<BaseLayout <BaseLayout
title="Contact Us — Get a Free Quote" title="Contact Us — Get a Free Project Quote Today"
description="Start your project with WorkRoot IT Solutions LLP. 20+ projects delivered, 100% client satisfaction, response within 24 hours. Reach us at admin@workroot.in or call +91 9561417403." description="Start your web, mobile, ERP, or government IT project with WorkRoot. 20+ projects delivered, replies within 1 business day. Email admin@workroot.in to begin."
> >
<SEO <SEO
slot="head" slot="head"
+2 -2
View File
@@ -8,8 +8,8 @@ const lastUpdated = 'March 20, 2026';
--- ---
<BaseLayout <BaseLayout
title="Cookie Policy" title="Cookie Policy — How We Use Cookies & Tracking"
description="WorkRoot IT Solutions Cookie Policy - Learn how we use cookies and similar tracking technologies on our website." description="Learn how WorkRoot IT Solutions uses cookies and similar tracking technologies, what each cookie does, and how to manage or disable them in your browser."
noIndex={true} noIndex={true}
> >
<!-- Hero Section --> <!-- Hero Section -->
+136 -15
View File
@@ -44,8 +44,8 @@ const services = [
slug: 'government-systems', slug: 'government-systems',
title: 'Government IT Solutions', title: 'Government IT Solutions',
outcome: 'Roll out secure, compliant platforms across districts without rewriting them for each one.', outcome: 'Roll out secure, compliant platforms across districts without rewriting them for each one.',
description: 'Multi-district deployments with role-based access, audit trails, and the uptime public services require.', description: 'GeM-empanelled and already live across 11+ districts at 99.9% uptime — role-based access, audit trails, and the security public services demand.',
features: ['Multi-District Deployments', 'Secure & Compliant', 'High Availability'], features: ['Live in 11+ Districts', 'GeM-Empanelled & Compliant', '99.9% Uptime'],
cta: 'Discuss your government project', cta: 'Discuss your government project',
}, },
]; ];
@@ -124,7 +124,7 @@ const testimonials = [
]; ];
// NOTE: The homepage stat band is rendered once by <ProofNumbers />, which // 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 // 50,000+ Records · 8+ Years). The previously-duplicated "Numbers That
// Matter" stats section (and its local `stats` array + count-up script) was // 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. // 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 // "Common Questions" block that used to live above the tech-stack section
// was removed; its unique questions were merged into HomepageFaq's // was removed; its unique questions were merged into HomepageFaq's
// `moneyFaqs` array so no buyer-facing content was dropped. // `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 <BaseLayout
title="Home" title="Home"
description="WorkRoot IT Solutions LLP builds production-grade web, mobile, ERP, and government IT systems for enterprises and growing teams. Free consultation, reply within 1 business day." 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 <SEO
slot="head" slot="head"
@@ -208,11 +239,11 @@ const techStack = [
<svg class="w-5 h-5 text-accent-400" fill="currentColor" viewBox="0 0 20 20" aria-hidden="true"> <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"/> <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> </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> </div>
<!-- Main heading — benefit-led: outcome + audience --> <!-- Main heading — benefit-led: outcome + audience -->
<h1 class="text-4xl md:text-5xl lg:text-6xl font-bold text-white leading-[1.1] tracking-tight"> <h1 class="text-3xl sm:text-4xl md:text-5xl lg:text-6xl font-bold text-white leading-[1.1] tracking-tight">
<span class="block opacity-0 animate-slide-up" style="animation-delay: 0.2s;">Custom Software That</span> <span class="block opacity-0 animate-slide-up" style="animation-delay: 0.2s;">Custom Software That</span>
<span class="block opacity-0 animate-slide-up" style="animation-delay: 0.4s;">Powers <span class="text-transparent bg-clip-text bg-gradient-to-r from-primary-400 via-primary-300 to-accent-400">Enterprises &amp; Governments</span></span> <span class="block opacity-0 animate-slide-up" style="animation-delay: 0.4s;">Powers <span class="text-transparent bg-clip-text bg-gradient-to-r from-primary-400 via-primary-300 to-accent-400">Enterprises &amp; Governments</span></span>
</h1> </h1>
@@ -420,17 +451,31 @@ const techStack = [
))} ))}
</ul> </ul>
<!-- Learn more arrow --> <!-- Per-service CTAs — a primary conversion link that routes
straight to the contact/quote form (tagged per service so
the lead source is attributable), plus a secondary
"Learn more" link into the matching /services detail
section for visitors who want depth before they reach out. -->
<div class="mt-7 flex flex-wrap items-center gap-x-6 gap-y-3">
<a <a
href={`/services#${service.slug}`} href={`/contact?source=service-${service.slug}`}
class="mt-6 inline-flex items-center text-primary-600 dark:text-primary-400 font-semibold group-hover:text-primary-700 dark:group-hover:text-primary-300 transition-colors focus:outline-none focus:underline" data-cta={`service-${service.slug}`}
aria-label={`Learn more about ${service.title}`} class="inline-flex items-center rounded-xl bg-primary-50 dark:bg-primary-900/30 px-5 py-3 text-base font-bold text-primary-700 dark:text-primary-300 transition-all hover:bg-primary-100 dark:hover:bg-primary-900/50 hover:scale-105 focus:outline-none focus:ring-2 focus:ring-primary-400 focus:ring-offset-2 focus:ring-offset-white dark:focus:ring-offset-secondary-800"
aria-label={`${service.cta} — get a free quote`}
> >
<span>Learn more</span> <span>{service.cta}</span>
<svg class="w-5 h-5 ml-2 group-hover:translate-x-2 transition-transform" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true"> <svg class="w-5 h-5 ml-2 transition-transform group-hover:translate-x-1" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 8l4 4m0 0l-4 4m4-4H3"></path> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 8l4 4m0 0l-4 4m4-4H3"></path>
</svg> </svg>
</a> </a>
<a
href={`/services#${service.slug}`}
class="inline-flex items-center text-secondary-500 dark:text-secondary-400 font-semibold hover:text-primary-600 dark:hover:text-primary-300 transition-colors focus:outline-none focus:underline"
aria-label={`Learn more about ${service.title}`}
>
<span>Learn more</span>
</a>
</div>
</div> </div>
<!-- Number badge --> <!-- Number badge -->
@@ -667,6 +712,80 @@ const techStack = [
</div> </div>
</section> </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 <!-- Technologies We Work With — relocated from the hero so social proof
leads above the fold. Kept intentionally subdued (border-only cards, leads above the fold. Kept intentionally subdued (border-only cards,
neutral grayscale icons) so it reads as supporting context rather than neutral grayscale icons) so it reads as supporting context rather than
@@ -730,9 +849,11 @@ const techStack = [
<!-- Inline Lead Form — sits between the tech-stack social-proof block <!-- Inline Lead Form — sits between the tech-stack social-proof block
and the final CTA band so visitors who are ready to start a project can and the final CTA band so visitors who are ready to start a project can
submit a 3-field enquiry without leaving the homepage. Submits to the submit a compact enquiry (name, email, phone, project type, message)
same /api/contact endpoint and is tagged `source: 'homepage-inline'` so without leaving the homepage. Required fields are kept to name + email +
the lead can be segmented downstream. --> project type for a low-friction mobile flow. Submits to the same
/api/contact endpoint and is tagged `source: 'homepage-inline'` so the
lead can be segmented downstream. -->
<InlineLeadForm /> <InlineLeadForm />
<!-- Homepage FAQ — single canonical FAQ section for /. Covers the cost / <!-- Homepage FAQ — single canonical FAQ section for /. Covers the cost /
+2 -2
View File
@@ -3,8 +3,8 @@ import BaseLayout from '../layouts/BaseLayout.astro';
--- ---
<BaseLayout <BaseLayout
title="You're Offline" title="You're Offline — Reconnect to Keep Browsing"
description="It looks like you're offline. Please check your internet connection." description="You appear to be offline. Check your internet connection and try again — many WorkRoot IT Solutions pages stay available once you have visited them before."
noIndex={true} noIndex={true}
> >
<section class="min-h-[60vh] flex items-center justify-center px-4"> <section class="min-h-[60vh] flex items-center justify-center px-4">
+3 -3
View File
@@ -173,8 +173,8 @@ const featuredProjects = projects.filter(p => p.featured);
--- ---
<BaseLayout <BaseLayout
title="Portfolio Case Studies & Projects" title="Software Project Portfolio & Case Studies"
description="Explore WorkRoot IT Solutions' portfolio: 20+ successful projects across web, mobile, ERP & government IT. From PLNR Financing to Bihar Government systems. Real results: 100% client satisfaction, 99.9% uptime." description="Explore WorkRoot's portfolio of 20+ web, mobile, ERP, and government IT projects — from PLNR Financing to Bihar police systems, with 99.9% uptime delivered."
> >
<SEO <SEO
slot="head" slot="head"
@@ -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"> <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: '20+', label: 'Projects' },
{ value: '11', label: 'Districts Deployed' }, { value: '11+', label: 'Districts Deployed' },
{ value: '50,000+', label: 'Records Managed' }, { value: '50,000+', label: 'Records Managed' },
{ value: '8+', label: 'Years Avg Experience' }, { value: '8+', label: 'Years Avg Experience' },
].map((stat) => ( ].map((stat) => (
+2 -2
View File
@@ -8,8 +8,8 @@ const lastUpdated = 'March 20, 2026';
--- ---
<BaseLayout <BaseLayout
title="Privacy Policy" title="Privacy Policy — How We Protect Your Data"
description="WorkRoot IT Solutions Privacy Policy - Learn how we collect, use, and protect your personal information." description="Read the WorkRoot IT Solutions privacy policy to learn what personal data we collect, how we use and store it, and the choices and rights you have over it."
noIndex={true} noIndex={true}
> >
<!-- Hero Section --> <!-- Hero Section -->
+41 -2
View File
@@ -246,6 +246,23 @@ const caseStudies = [
}, },
]; ];
// Dedicated, keyword-focused landing pages that deep-dive a service. Keyed by
// the service `id` so each detail section can surface its related page(s)
// directly in the on-page navigation flow.
const landingPages: Record<string, { label: string; href: string }[]> = {
'government-systems': [
{ label: 'Government IT & GeM vendor', href: '/services/government-it-solutions-gem-vendor' },
{ label: 'Police Management System', href: '/services/police-management-system' },
],
'erp-solutions': [
{ label: 'Custom ERP development', href: '/services/custom-erp-development' },
{ label: 'University ERP', href: '/services/university-erp' },
],
'mobile-development': [
{ label: 'React Native app development', href: '/services/react-native-app-development' },
],
};
// Process steps // Process steps
const processSteps = [ const processSteps = [
{ {
@@ -276,8 +293,8 @@ const processSteps = [
--- ---
<BaseLayout <BaseLayout
title="IT Services & Solutions" title="ERP, Web & Mobile App Development Services"
description="Expert web development, mobile apps, ERP systems, government IT solutions & UI/UX design from WorkRoot IT Solutions LLP. 10+ projects delivered, 8+ happy clients. From ₹1,00,000 — get a free quote today." 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 <SEO
slot="head" slot="head"
@@ -545,6 +562,28 @@ const processSteps = [
<path stroke-linecap="round" stroke-linejoin="round" d="M17 8l4 4m0 0l-4 4m4-4H3" /> <path stroke-linecap="round" stroke-linejoin="round" d="M17 8l4 4m0 0l-4 4m4-4H3" />
</svg> </svg>
</a> </a>
{/* Deep-dive landing pages related to this service */}
{landingPages[service.id] && (
<div class="mt-6 pt-5 border-t border-secondary-200 dark:border-secondary-700">
<p class="text-xs font-semibold text-secondary-400 dark:text-secondary-500 uppercase tracking-wider mb-2">Explore in depth</p>
<ul class="flex flex-wrap gap-x-5 gap-y-2">
{landingPages[service.id].map((page) => (
<li>
<a
href={page.href}
class={`inline-flex items-center gap-1.5 text-sm font-semibold ${service.iconColor} hover:underline focus:outline-none focus:underline`}
>
{page.label}
<svg xmlns="http://www.w3.org/2000/svg" class="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2" aria-hidden="true">
<path stroke-linecap="round" stroke-linejoin="round" d="M9 5l7 7-7 7" />
</svg>
</a>
</li>
))}
</ul>
</div>
)}
</div> </div>
<!-- Visual element --> <!-- Visual element -->
@@ -0,0 +1,307 @@
---
// Custom ERP Development — keyword-focused service landing page.
// Reuses the shared section components (GuaranteeBand, InlineLeadForm,
// 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 ·
// 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';
import SEO from '../../components/SEO.astro';
import GuaranteeBand from '../../components/GuaranteeBand.astro';
import InlineLeadForm from '../../components/InlineLeadForm.astro';
import ServiceFaq from '../../components/ServiceFaq.astro';
const pageTitle = 'Custom ERP Development Services';
const pageDescription =
'Custom ERP development by WorkRoot — bespoke enterprise systems on Spring Boot, Angular & Next.js. Already running for IIT Patna and across 11 government districts. Get a free scope.';
const serviceName = 'Custom ERP Development';
const serviceDescription =
'Bespoke ERP and enterprise resource planning systems built around your real workflows — modules for operations, personnel, finance, inventory, reporting and role-based access. Delivered with Spring Boot, Angular, and Next.js, and proven in university and multi-district government deployments.';
// Capabilities — what a WorkRoot custom ERP build typically covers.
const modules = [
{ title: 'Operations & workflow', body: 'Model your real approval chains, hand-offs, and SLAs instead of bending the business to a rigid template.' },
{ title: 'Role-based access control', body: 'Granular permissions per role, department, and district — the same RBAC layer running in our government deployments.' },
{ title: 'Personnel & HR', body: 'Staff records, duty rosters, leave, and audit trails — the backbone of our 15,000+ officer police deployment.' },
{ title: 'Inventory & assets', body: 'Stock, procurement, and asset lifecycle tracking with reconciliation and low-stock alerting.' },
{ title: 'Dashboards & reporting', body: 'Real-time operational dashboards and scheduled exports for leadership oversight and compliance.' },
{ title: 'Integrations & APIs', body: 'Clean REST APIs so your ERP talks to payment gateways, SMS/email, and existing legacy systems.' },
];
// Comparison table — off-the-shelf ERP vs a WorkRoot custom build.
const comparison = [
{ dimension: 'Fit to your workflow', offShelf: 'You adapt your process to the software', custom: 'The software is built around your process' },
{ dimension: 'Per-seat licensing', offShelf: 'Recurring per-user fees that scale with headcount', custom: 'You own the source — no per-seat tax' },
{ dimension: 'Source-code ownership', offShelf: 'Vendor-locked, closed source', custom: 'Full IP transfer on delivery' },
{ dimension: 'Customisation', offShelf: 'Limited to vendor-exposed settings', custom: 'Any module, any rule, any report' },
{ dimension: 'Data residency', offShelf: 'Often the vendors cloud / region', custom: 'Your servers, your region, your control' },
{ dimension: 'Hosting model', offShelf: 'SaaS only', custom: 'On-prem, your VPC, or managed by us' },
];
const stats = [
{ value: '5+', label: 'ERP & enterprise deployments' },
{ value: '20,000+', label: 'Users across systems' },
{ value: '11+', label: 'Districts running our platforms' },
{ value: '99.9%', label: 'Uptime on critical systems' },
];
const faqs = [
{
question: 'How much does a custom ERP cost?',
answer:
'Custom ERP engagements typically run ₹2,00,000–₹15,00,000 depending on the number of modules, user roles, and integrations. We offer fixed-price quotes for well-defined scopes and time-and-materials for evolving ones. Every estimate is free and itemised.',
},
{
question: 'How long does it take to build a custom ERP?',
answer:
'Most ERP builds ship a usable first module in 68 weeks and reach full rollout in 36 months. 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 big-bang launch.',
},
{
question: 'Do we own the source code?',
answer:
'Yes. Full IP transfer is included — you receive the repositories, deployment access, and documentation on delivery. There are no per-seat licence fees, and you are free to host, extend, or hand the system to another team at any time.',
},
{
question: 'Can you integrate with our existing systems?',
answer:
'Yes. We build clean REST APIs and have integrated ERPs with payment gateways, SMS and email providers, government identity systems, and legacy MySQL/PostgreSQL databases. If your current tool can export data, we can migrate and connect to it.',
},
{
question: 'Is a custom ERP secure enough for sensitive data?',
answer:
'Security is built into every layer: role-based access control, encrypted data at rest and in transit, full audit logging, and OWASP-aligned reviews. The same architecture currently runs a Bihar government police platform across 11 districts under strict security compliance.',
},
{
question: 'What technology stack do you use for ERP?',
answer:
'We build on Spring Boot for the backend, Angular or Next.js for the admin and user interfaces, and PostgreSQL or MySQL for the data layer — the same stack behind the IIT Patna University Management System and our multi-district government deployments.',
},
];
---
<BaseLayout title={pageTitle} description={pageDescription}>
<SEO
slot="head"
type="Service"
breadcrumbs={[
{ name: 'Home', url: '/' },
{ name: 'Services', url: '/services' },
{ name: 'Custom ERP Development', url: '/services/custom-erp-development' },
]}
service={{ name: serviceName, description: serviceDescription }}
faq={faqs}
/>
<!-- Hero -->
<section class="relative bg-gradient-to-br from-secondary-900 via-secondary-800 to-secondary-900 py-20 lg:py-28 overflow-hidden">
<div class="absolute inset-0 overflow-hidden pointer-events-none" aria-hidden="true">
<div class="absolute top-1/4 left-0 w-80 h-80 bg-primary-500/10 rounded-full blur-3xl animate-pulse-slower"></div>
<div class="absolute bottom-0 right-1/4 w-96 h-96 bg-purple-500/10 rounded-full blur-3xl animate-pulse-slow"></div>
<div class="absolute inset-0 opacity-5" style="background-image: linear-gradient(rgba(255,255,255,.05) 1px, transparent 1px), linear-gradient(90deg, rgba(255,255,255,.05) 1px, transparent 1px); background-size: 60px 60px;"></div>
</div>
<div class="container-wrapper relative">
<div class="max-w-3xl">
<nav aria-label="Breadcrumb" class="mb-6">
<ol class="flex flex-wrap items-center gap-2 text-sm text-secondary-400">
<li><a href="/" class="hover:text-primary-300">Home</a></li>
<li aria-hidden="true">/</li>
<li><a href="/services" class="hover:text-primary-300">Services</a></li>
<li aria-hidden="true">/</li>
<li class="text-secondary-200" aria-current="page">Custom ERP Development</li>
</ol>
</nav>
<span class="inline-flex items-center gap-2 px-4 py-1.5 mb-6 text-sm font-semibold text-primary-300 bg-primary-500/10 rounded-full border border-primary-500/20">
<span class="w-1.5 h-1.5 rounded-full bg-primary-400 animate-pulse" aria-hidden="true"></span>
ERP &amp; Enterprise Systems
</span>
<h1 class="text-4xl sm:text-5xl lg:text-6xl font-bold text-white mb-6 leading-tight">
Custom ERP development that fits
<span class="text-transparent bg-clip-text bg-gradient-to-r from-primary-400 via-primary-300 to-accent-400"> your workflow, not a template.</span>
</h1>
<p class="text-lg sm:text-xl text-secondary-300 max-w-2xl mb-8 leading-relaxed">
Replace scattered spreadsheets and disconnected tools with one bespoke system your team actually trusts —
built on Spring Boot, Angular, and Next.js, and already running for IIT Patna and across 11 government districts.
</p>
<div class="flex flex-col sm:flex-row gap-4">
<a href="#inline-lead-form" data-cta="erp-hero-primary" class="inline-flex items-center justify-center rounded-xl bg-gradient-to-r from-primary-500 to-primary-600 px-8 py-4 text-base font-bold text-white shadow-lg hover:shadow-xl hover:scale-105 transition-all focus:outline-none focus:ring-2 focus:ring-primary-400 focus:ring-offset-2 focus:ring-offset-secondary-900">
Get my free ERP scope
<svg class="ml-2 w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2.5" d="M17 8l4 4m0 0l-4 4m4-4H3" /></svg>
</a>
<a href="tel:+919561417403" data-cta="erp-hero-call" class="inline-flex items-center justify-center rounded-xl border-2 border-white/30 bg-white/5 px-8 py-4 text-base font-bold text-white hover:bg-white/10 transition-all focus:outline-none focus:ring-2 focus:ring-white/50">
Talk to an engineer
</a>
</div>
</div>
<!-- Stat block -->
<div class="mt-14 grid grid-cols-2 sm:grid-cols-4 gap-4 lg:gap-6">
{stats.map((stat) => (
<div class="bg-white/5 backdrop-blur-sm rounded-xl p-4 lg:p-5 text-center border border-white/10">
<p class="text-2xl lg:text-3xl font-bold text-white">{stat.value}</p>
<p class="text-secondary-400 text-sm mt-1">{stat.label}</p>
</div>
))}
</div>
</div>
</section>
<!-- Overview / prose -->
<section class="py-16 lg:py-24 bg-white dark:bg-secondary-900">
<div class="container-wrapper">
<div class="max-w-3xl mx-auto">
<span class="text-primary-500 dark:text-primary-400 font-semibold text-sm uppercase tracking-wider">What we build</span>
<h2 class="text-3xl sm:text-4xl font-bold text-secondary-900 dark:text-secondary-100 mt-2 mb-6">Enterprise software shaped around how you actually operate</h2>
<div class="space-y-5 text-lg text-secondary-700 dark:text-secondary-300 leading-relaxed">
<p>
An off-the-shelf ERP forces every organisation into the same boxes. That works until your approval chain, your
district structure, or your compliance rules dont fit the template — and then you are paying per seat for software
your team quietly works around in spreadsheets. A custom ERP flips that relationship: the system is built around the
way you already run, so adoption is high and the workarounds disappear.
</p>
<p>
WorkRoot has been delivering bespoke enterprise systems since 2022, and the engineers on our team carry 8+ years of
experience building software that has to stay up. We have shipped enterprise platforms for an IIT, for a state
government across 11 districts, and for private operators handling tens of thousands of users. Across those systems
we manage <strong>50,000+ records</strong> and hold <strong>99.9% uptime</strong> on the deployments that cannot
afford to go dark.
</p>
<p>
Every engagement starts with your workflow, not our codebase. We map the real process — who requests what, who
approves it, where it gets stuck — and then build modules that remove the friction: operations and workflow,
role-based access, personnel and HR, inventory and assets, finance, and the dashboards leadership needs to see it
all. The result is one system of record instead of five disconnected tools, and a single source of truth your
auditors will thank you for.
</p>
<p>
Because the system is yours, there are no per-seat licence fees and no vendor lock-in. We hand over the full source
code, the repositories, the deployment pipeline, and the documentation. You can host it on-premise, in your own
cloud account, or let us run it for you under a maintenance retainer — the choice stays with you.
</p>
</div>
</div>
</div>
</section>
<!-- Modules grid -->
<section class="py-16 lg:py-24 bg-secondary-50 dark:bg-secondary-800/50">
<div class="container-wrapper">
<div class="text-center max-w-2xl mx-auto mb-12">
<h2 class="text-3xl sm:text-4xl font-bold text-secondary-900 dark:text-secondary-100 mb-4">Modules we build into custom ERPs</h2>
<p class="text-secondary-600 dark:text-secondary-400 text-lg">Mix and match the pieces your operation needs — each one production-proven in a live deployment.</p>
</div>
<div class="grid sm:grid-cols-2 lg:grid-cols-3 gap-6">
{modules.map((m) => (
<div class="bg-white dark:bg-secondary-800 rounded-2xl p-7 border border-secondary-100 dark:border-secondary-700 shadow-sm hover:shadow-lg transition-shadow">
<div class="w-12 h-12 rounded-xl bg-primary-50 dark:bg-primary-900/30 text-primary-600 dark:text-primary-300 flex items-center justify-center mb-4" aria-hidden="true">
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M5 13l4 4L19 7" /></svg>
</div>
<h3 class="text-lg font-bold text-secondary-900 dark:text-secondary-100 mb-2">{m.title}</h3>
<p class="text-secondary-600 dark:text-secondary-400 text-sm leading-relaxed">{m.body}</p>
</div>
))}
</div>
</div>
</section>
<!-- Comparison table -->
<section class="py-16 lg:py-24 bg-white dark:bg-secondary-900">
<div class="container-wrapper">
<div class="max-w-4xl mx-auto">
<div class="text-center mb-10">
<h2 class="text-3xl sm:text-4xl font-bold text-secondary-900 dark:text-secondary-100 mb-4">Off-the-shelf ERP vs a custom build</h2>
<p class="text-secondary-600 dark:text-secondary-400 text-lg">Where a bespoke system pays you back.</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 a custom ERP built by WorkRoot</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 ERP (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>
<!-- Case study -->
<section class="py-16 lg:py-24 bg-gradient-to-br from-secondary-900 via-secondary-800 to-secondary-900">
<div class="container-wrapper">
<div class="max-w-4xl mx-auto">
<span class="inline-flex items-center gap-2 px-4 py-1.5 mb-5 text-sm font-semibold text-purple-300 bg-purple-500/10 rounded-full border border-purple-500/20">Case study · Enterprise ERP</span>
<h2 class="text-3xl sm:text-4xl font-bold text-white mb-4">University Management System — IIT Patna</h2>
<p class="text-lg text-secondary-300 leading-relaxed mb-8">
IIT Patna needed a single platform to replace fragmented legacy systems across admissions, course registration,
examination scheduling, grade management, and faculty administration. We built an end-to-end ERP on a
<strong class="text-white"> Next.js</strong> front end with a <strong class="text-white">Spring Boot</strong> backend
and a <strong class="text-white">MySQL</strong> data layer — designed for scalability and high availability across the
entire academic community.
</p>
<div class="grid grid-cols-3 gap-4 mb-8">
{[{ v: '3,000+', l: 'Students served' }, { v: '500+', l: 'Courses managed' }, { v: '200+', l: 'Faculty onboarded' }].map((s) => (
<div class="bg-white/5 border border-white/10 rounded-xl p-5 text-center">
<p class="text-2xl sm:text-3xl font-bold text-white">{s.v}</p>
<p class="text-secondary-400 text-xs sm:text-sm mt-1">{s.l}</p>
</div>
))}
</div>
<blockquote class="border-l-4 border-primary-500 pl-5 text-secondary-200 italic leading-relaxed">
“WorkRoot delivered our University Management System for IIT Patna on time and within budget. The Next.js + Spring Boot
architecture is robust and scalable. They are a reliable technology partner for complex enterprise projects.”
<footer class="mt-3 not-italic text-sm text-secondary-400">— Dilip Raj, Director, MOC Soft Technologies Pvt Ltd</footer>
</blockquote>
<div class="mt-8 flex flex-wrap gap-4">
<a href="/portfolio#university-management-iitp" class="inline-flex items-center gap-2 text-primary-300 font-semibold hover:text-primary-200">
Read the full case study
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" d="M17 8l4 4m0 0l-4 4m4-4H3" /></svg>
</a>
<a href="/services/university-erp" class="inline-flex items-center gap-2 text-secondary-300 font-semibold hover:text-white">
Explore our University ERP page
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" d="M9 5l7 7-7 7" /></svg>
</a>
</div>
</div>
</div>
</section>
<!-- More deployments strip -->
<section class="py-12 bg-secondary-50 dark:bg-secondary-800/50 border-y border-secondary-100 dark:border-secondary-800">
<div class="container-wrapper">
<p class="text-center text-sm font-semibold uppercase tracking-wider text-secondary-500 dark:text-secondary-400 mb-6">More enterprise systems weve shipped</p>
<div class="flex flex-wrap items-center justify-center gap-3">
<a href="/services/police-management-system" class="px-4 py-2 rounded-full bg-white dark:bg-secondary-800 border border-secondary-200 dark:border-secondary-700 text-sm font-medium text-secondary-700 dark:text-secondary-300 hover:border-primary-300 transition-colors">Police Management System · 11 districts</a>
<a href="/services/government-it-solutions-gem-vendor" class="px-4 py-2 rounded-full bg-white dark:bg-secondary-800 border border-secondary-200 dark:border-secondary-700 text-sm font-medium text-secondary-700 dark:text-secondary-300 hover:border-primary-300 transition-colors">SCMS for PSCL, Bihar Govt · 50,000+ records</a>
<a href="/portfolio#plnr-financing" class="px-4 py-2 rounded-full bg-white dark:bg-secondary-800 border border-secondary-200 dark:border-secondary-700 text-sm font-medium text-secondary-700 dark:text-secondary-300 hover:border-primary-300 transition-colors">PLNR Financing · 5,000+ users</a>
</div>
</div>
</section>
<ServiceFaq
faqs={faqs}
heading="Custom ERP — your questions answered"
subheading="Cost, timelines, ownership, integrations, and security for a bespoke ERP build."
/>
<GuaranteeBand />
<InlineLeadForm />
</BaseLayout>
@@ -0,0 +1,309 @@
---
// Government IT Solutions / GeM Vendor — keyword-focused service landing page.
// Reuses GuaranteeBand, InlineLeadForm and ServiceFaq, and feeds Service + FAQ
// schema into SEO.astro.
//
// Canonical stats mirror ProofNumbers.astro / /portfolio / SEO.astro:
// 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';
import InlineLeadForm from '../../components/InlineLeadForm.astro';
import ServiceFaq from '../../components/ServiceFaq.astro';
const pageTitle = 'Government IT Solutions & GeM Vendor';
const pageDescription =
'GeM-empanelled government IT vendor. WorkRoot builds secure, multi-district public-service platforms — live across 11 Bihar districts at 99.9% uptime. Get a free scope for your tender.';
const serviceName = 'Government IT Solutions';
const serviceDescription =
'Secure, compliant government IT systems for public bodies and smart-city missions — multi-district deployment, role-based access, audit logging, and data residency. Delivered by a GeM-empanelled vendor with live platforms across 11 districts in Bihar at 99.9% uptime.';
const capabilities = [
{ title: 'Multi-district deployment', body: 'One platform rolled out across districts without rewriting it for each — proven across 11 Bihar districts.' },
{ title: 'Citizen-facing portals', body: 'Public service portals for bookings, applications, and status tracking, built for low-bandwidth access.' },
{ title: 'Audit & compliance', body: 'Audit logging baked into the data layer, role-based access control, and tamper-evident record-keeping.' },
{ title: 'Smart-city systems (SCMS)', body: 'Operational systems for civic facilities — slot booking, real-time availability, and oversight dashboards.' },
{ title: 'Data residency & security', body: 'Hosting in your region under strict security standards — encrypted at rest and in transit.' },
{ title: 'High availability', body: 'Infrastructure engineered for 99.9% uptime on mission-critical public services.' },
];
// Capability / readiness stat block presented as a checklist table.
const readiness = [
{ dimension: 'GeM empanelment', detail: 'Empanelled for relevant IT service categories on the Government e-Marketplace.' },
{ dimension: 'Live government deployments', detail: 'Smart Crematorium Management System for Patna Smart City Limited (PSCL), Bihar Government.' },
{ dimension: 'Multi-district experience', detail: 'Police Management System deployed across 11 districts for the Bihar State Government.' },
{ dimension: 'Records under management', detail: '50,000+ government records managed across live deployments.' },
{ dimension: 'Security posture', detail: 'Role-based access control, full audit trails, OWASP-aligned reviews, encrypted data.' },
{ dimension: 'Uptime track record', detail: '99.9% uptime sustained on critical public-service systems.' },
];
const stats = [
{ value: '11', label: 'Districts deployed' },
{ value: '50,000+', label: 'Government records' },
{ value: '99.9%', label: 'Uptime' },
{ value: 'GeM', label: 'Empanelled vendor' },
];
const faqs = [
{
question: 'Is WorkRoot a GeM-empanelled vendor?',
answer:
'Yes. WorkRoot is empanelled for relevant IT service categories on the Government e-Marketplace (GeM), and we have delivered platforms used across 11 districts in Bihar. We can respond to GeM bids and government tenders for web, ERP, and smart-city systems.',
},
{
question: 'Have you delivered for government clients before?',
answer:
'Yes. We built the Smart Crematorium Management System (SCMS) for Patna Smart City Limited under the Bihar Government, and a Police Management System deployed across 11 districts for the Bihar State Government, handling personnel, case tracking, and FIR digitisation for 15,000+ officers.',
},
{
question: 'How do you handle data residency and security for government systems?',
answer:
'Government data stays in your chosen region on infrastructure you control. We apply role-based access control, encrypt data at rest and in transit, log every action for audit, and run OWASP-aligned security reviews. The same controls run our live Bihar government deployments under strict compliance.',
},
{
question: 'Can a single platform serve multiple districts?',
answer:
'Yes — that is exactly what our multi-district architecture is built for. We use a database-per-district model with central aggregation and district-specific feature configuration, so one codebase rolls out across districts without a separate rewrite for each.',
},
{
question: 'What uptime can you commit to for public services?',
answer:
'We sustain 99.9% uptime on our mission-critical government deployments through high-availability infrastructure, monitoring, and blue-green deployments. We can align an SLA to the criticality of your service during scoping.',
},
{
question: 'What technology do you use for government platforms?',
answer:
'We build on Spring Boot, Astro, and Golang with PostgreSQL and MySQL — the stack behind our SCMS and police deployments. The choice favours long-term maintainability, security, and performance under real public load.',
},
];
---
<BaseLayout title={pageTitle} description={pageDescription}>
<SEO
slot="head"
type="Service"
breadcrumbs={[
{ name: 'Home', url: '/' },
{ name: 'Services', url: '/services' },
{ name: 'Government IT Solutions', url: '/services/government-it-solutions-gem-vendor' },
]}
service={{ name: serviceName, description: serviceDescription }}
faq={faqs}
/>
<!-- Hero -->
<section class="relative bg-gradient-to-br from-secondary-900 via-secondary-800 to-secondary-900 py-20 lg:py-28 overflow-hidden">
<div class="absolute inset-0 overflow-hidden pointer-events-none" aria-hidden="true">
<div class="absolute top-1/4 left-0 w-80 h-80 bg-red-500/10 rounded-full blur-3xl animate-pulse-slower"></div>
<div class="absolute bottom-0 right-1/4 w-96 h-96 bg-orange-500/10 rounded-full blur-3xl animate-pulse-slow"></div>
<div class="absolute inset-0 opacity-5" style="background-image: linear-gradient(rgba(255,255,255,.05) 1px, transparent 1px), linear-gradient(90deg, rgba(255,255,255,.05) 1px, transparent 1px); background-size: 60px 60px;"></div>
</div>
<div class="container-wrapper relative">
<div class="max-w-3xl">
<nav aria-label="Breadcrumb" class="mb-6">
<ol class="flex flex-wrap items-center gap-2 text-sm text-secondary-400">
<li><a href="/" class="hover:text-primary-300">Home</a></li>
<li aria-hidden="true">/</li>
<li><a href="/services" class="hover:text-primary-300">Services</a></li>
<li aria-hidden="true">/</li>
<li class="text-secondary-200" aria-current="page">Government IT Solutions</li>
</ol>
</nav>
<span class="inline-flex items-center gap-2 px-4 py-1.5 mb-6 text-sm font-semibold text-red-300 bg-red-500/10 rounded-full border border-red-500/20">
<span class="w-1.5 h-1.5 rounded-full bg-red-400 animate-pulse" aria-hidden="true"></span>
GeM-empanelled · Government IT
</span>
<h1 class="text-4xl sm:text-5xl lg:text-6xl font-bold text-white mb-6 leading-tight">
Government IT systems that
<span class="text-transparent bg-clip-text bg-gradient-to-r from-red-400 via-orange-300 to-amber-300"> roll out across districts and stay up.</span>
</h1>
<p class="text-lg sm:text-xl text-secondary-300 max-w-2xl mb-8 leading-relaxed">
A GeM-empanelled vendor with secure, audit-ready platforms already live across 11 Bihar districts at 99.9% uptime.
Built on Spring Boot, Astro, Golang and PostgreSQL — with the role-based access, data residency, and compliance public
services demand.
</p>
<div class="flex flex-col sm:flex-row gap-4">
<a href="#inline-lead-form" data-cta="gov-hero-primary" class="inline-flex items-center justify-center rounded-xl bg-gradient-to-r from-primary-500 to-primary-600 px-8 py-4 text-base font-bold text-white shadow-lg hover:shadow-xl hover:scale-105 transition-all focus:outline-none focus:ring-2 focus:ring-primary-400 focus:ring-offset-2 focus:ring-offset-secondary-900">
Scope my government project
<svg class="ml-2 w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2.5" d="M17 8l4 4m0 0l-4 4m4-4H3" /></svg>
</a>
<a href="tel:+919561417403" data-cta="gov-hero-call" class="inline-flex items-center justify-center rounded-xl border-2 border-white/30 bg-white/5 px-8 py-4 text-base font-bold text-white hover:bg-white/10 transition-all focus:outline-none focus:ring-2 focus:ring-white/50">
Talk to our team
</a>
</div>
</div>
<div class="mt-14 grid grid-cols-2 sm:grid-cols-4 gap-4 lg:gap-6">
{stats.map((stat) => (
<div class="bg-white/5 backdrop-blur-sm rounded-xl p-4 lg:p-5 text-center border border-white/10">
<p class="text-2xl lg:text-3xl font-bold text-white">{stat.value}</p>
<p class="text-secondary-400 text-sm mt-1">{stat.label}</p>
</div>
))}
</div>
</div>
</section>
<!-- Overview -->
<section class="py-16 lg:py-24 bg-white dark:bg-secondary-900">
<div class="container-wrapper">
<div class="max-w-3xl mx-auto">
<span class="text-red-500 dark:text-red-400 font-semibold text-sm uppercase tracking-wider">Why public bodies choose us</span>
<h2 class="text-3xl sm:text-4xl font-bold text-secondary-900 dark:text-secondary-100 mt-2 mb-6">Digitise public services without the rewrite-for-each-district tax</h2>
<div class="space-y-5 text-lg text-secondary-700 dark:text-secondary-300 leading-relaxed">
<p>
Public-sector technology fails in predictable ways: a system built for one office cant scale to the next district,
paper processes survive alongside half-finished portals, and security or audit requirements get bolted on too late.
WorkRoot builds government platforms the other way around — multi-district from day one, with audit logging and
role-based access designed into the data layer rather than retrofitted.
</p>
<p>
We are empanelled on the Government e-Marketplace (GeM) and have delivered for real public clients. For
<strong> Patna Smart City Limited</strong> under the Bihar Government we built and shipped a Smart Crematorium
Management System that now manages <strong>50,000+ records</strong> across 10+ facilities. For the Bihar State
Government we delivered a Police Management System deployed across <strong>11 districts</strong>, digitising FIRs,
case tracking, and personnel administration for <strong>15,000+ officers</strong>. Both run at 99.9% uptime.
</p>
<p>
Our architecture is deliberately conservative where it counts. We favour a database-per-district model with central
aggregation so each districts data stays isolated while leadership still gets a consolidated view. Offline-resilient
design keeps field-critical modules working through patchy connectivity. Every sensitive action is written to an
immutable audit log, access is governed by role-based permissions, and data is encrypted both at rest and in transit.
Data residency stays under your control — on-premise, in your government cloud, or managed by us within your region.
</p>
<p>
Just as importantly, you own what we build. Full source-code ownership, documentation, and deployment access are part
of delivery, so your department is never hostage to a single vendor. That combination — GeM empanelment, a real
multi-district track record, and clean handover — is why public bodies trust us with services citizens depend on.
</p>
</div>
</div>
</div>
</section>
<!-- Capabilities grid -->
<section class="py-16 lg:py-24 bg-secondary-50 dark:bg-secondary-800/50">
<div class="container-wrapper">
<div class="text-center max-w-2xl mx-auto mb-12">
<h2 class="text-3xl sm:text-4xl font-bold text-secondary-900 dark:text-secondary-100 mb-4">What we deliver for government</h2>
<p class="text-secondary-600 dark:text-secondary-400 text-lg">Every capability below is running in a live public-sector deployment today.</p>
</div>
<div class="grid sm:grid-cols-2 lg:grid-cols-3 gap-6">
{capabilities.map((c) => (
<div class="bg-white dark:bg-secondary-800 rounded-2xl p-7 border border-secondary-100 dark:border-secondary-700 shadow-sm hover:shadow-lg transition-shadow">
<div class="w-12 h-12 rounded-xl bg-red-50 dark:bg-red-900/30 text-red-600 dark:text-red-300 flex items-center justify-center mb-4" aria-hidden="true">
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M9 12l2 2 4-4m5.618-4.016A11.955 11.955 0 0112 2.944a11.955 11.955 0 01-8.618 3.04A12.02 12.02 0 003 9c0 5.591 3.824 10.29 9 11.622 5.176-1.332 9-6.03 9-11.622 0-1.042-.133-2.052-.382-3.016z" /></svg>
</div>
<h3 class="text-lg font-bold text-secondary-900 dark:text-secondary-100 mb-2">{c.title}</h3>
<p class="text-secondary-600 dark:text-secondary-400 text-sm leading-relaxed">{c.body}</p>
</div>
))}
</div>
</div>
</section>
<!-- Readiness table -->
<section class="py-16 lg:py-24 bg-white dark:bg-secondary-900">
<div class="container-wrapper">
<div class="max-w-4xl mx-auto">
<div class="text-center mb-10">
<h2 class="text-3xl sm:text-4xl font-bold text-secondary-900 dark:text-secondary-100 mb-4">Tender-ready at a glance</h2>
<p class="text-secondary-600 dark:text-secondary-400 text-lg">What we bring to a government engagement.</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-[560px]">
<caption class="sr-only">WorkRoot government IT readiness and track record</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">Criterion</th>
<th scope="col" class="px-5 py-4 text-sm font-bold text-primary-700 dark:text-primary-300">WorkRoot</th>
</tr>
</thead>
<tbody>
{readiness.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 w-1/3">{row.dimension}</th>
<td class="px-5 py-4 text-sm text-secondary-700 dark:text-secondary-200 align-top">
<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.detail}
</span>
</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
</div>
</section>
<!-- Case studies (two government projects) -->
<section class="py-16 lg:py-24 bg-gradient-to-br from-secondary-900 via-secondary-800 to-secondary-900">
<div class="container-wrapper">
<div class="max-w-5xl mx-auto">
<div class="text-center mb-12">
<span class="inline-flex items-center gap-2 px-4 py-1.5 mb-4 text-sm font-semibold text-red-300 bg-red-500/10 rounded-full border border-red-500/20">Government case studies</span>
<h2 class="text-3xl sm:text-4xl font-bold text-white">Live public-sector deployments</h2>
</div>
<div class="grid lg:grid-cols-2 gap-8">
<!-- SCMS -->
<article class="bg-white/5 border border-white/10 rounded-2xl p-8">
<h3 class="text-xl font-bold text-white mb-2">SCMS for PSCL, Bihar Government</h3>
<p class="text-sm text-secondary-400 mb-4">Patna Smart City Limited · Astro · Golang · PostgreSQL</p>
<p class="text-secondary-300 leading-relaxed mb-6">
A Smart Crematorium Management System digitising slot booking, real-time availability, digital record-keeping, and
automated reporting — cutting booking from hours to minutes across 10+ facilities.
</p>
<div class="grid grid-cols-3 gap-3">
{[{ v: '10+', l: 'Facilities' }, { v: '50,000+', l: 'Records' }, { v: '99.9%', l: 'Uptime' }].map((s) => (
<div class="bg-white/5 rounded-lg p-3 text-center">
<p class="text-xl font-bold text-white">{s.v}</p>
<p class="text-secondary-400 text-xs mt-1">{s.l}</p>
</div>
))}
</div>
<a href="/portfolio#scms-pscl" class="mt-6 inline-flex items-center gap-2 text-primary-300 font-semibold hover:text-primary-200 text-sm">
Full case study
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" d="M9 5l7 7-7 7" /></svg>
</a>
</article>
<!-- Police -->
<article class="bg-white/5 border border-white/10 rounded-2xl p-8">
<h3 class="text-xl font-bold text-white mb-2">Police Management System — Bihar</h3>
<p class="text-sm text-secondary-400 mb-4">Bihar State Government · Spring Boot · Angular · MySQL</p>
<p class="text-secondary-300 leading-relaxed mb-6">
A centralised system across 11 districts handling personnel management, FIR digitisation, case tracking, and duty
roster scheduling — with role-based access and strict security compliance for thousands of concurrent users.
</p>
<div class="grid grid-cols-3 gap-3">
{[{ v: '11', l: 'Districts' }, { v: '15,000+', l: 'Officers' }, { v: '100,000+', l: 'Cases' }].map((s) => (
<div class="bg-white/5 rounded-lg p-3 text-center">
<p class="text-xl font-bold text-white">{s.v}</p>
<p class="text-secondary-400 text-xs mt-1">{s.l}</p>
</div>
))}
</div>
<a href="/services/police-management-system" class="mt-6 inline-flex items-center gap-2 text-primary-300 font-semibold hover:text-primary-200 text-sm">
Police system details
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" d="M9 5l7 7-7 7" /></svg>
</a>
</article>
</div>
</div>
</div>
</section>
<ServiceFaq
faqs={faqs}
eyebrow="For government buyers"
heading="Government IT &amp; GeM — your questions answered"
subheading="Empanelment, track record, security, data residency, and multi-district rollout."
/>
<GuaranteeBand />
<InlineLeadForm />
</BaseLayout>
@@ -0,0 +1,285 @@
---
// Police Management System — keyword-focused service landing page.
// Reuses GuaranteeBand, InlineLeadForm and ServiceFaq, and feeds Service + FAQ
// schema into SEO.astro.
//
// Case-study numbers are taken verbatim from the Police Management System
// entry on /portfolio: 11 districts · 15,000+ officers · 100,000+ cases.
import BaseLayout from '../../layouts/BaseLayout.astro';
import SEO from '../../components/SEO.astro';
import GuaranteeBand from '../../components/GuaranteeBand.astro';
import InlineLeadForm from '../../components/InlineLeadForm.astro';
import ServiceFaq from '../../components/ServiceFaq.astro';
const pageTitle = 'Police Management System Development';
const pageDescription =
'Custom Police Management System development — FIR digitisation, case tracking, personnel & duty rosters. WorkRoots platform runs across 11 Bihar districts for 15,000+ officers.';
const serviceName = 'Police Management System Development';
const serviceDescription =
'Custom Police Management System software for state and district law-enforcement bodies — FIR digitisation, case tracking, personnel management, duty-roster scheduling, and real-time reporting with role-based access control. Built on Spring Boot and Angular, deployed across 11 districts in Bihar for 15,000+ officers.';
const modules = [
{ title: 'FIR digitisation', body: 'Digital First Information Reports with structured capture, search, and status tracking — replacing paper registers.' },
{ title: 'Case tracking', body: 'End-to-end case lifecycle from registration to disposal, with 100,000+ cases tracked in production.' },
{ title: 'Personnel management', body: 'Officer records, postings, transfers, and service history for 15,000+ personnel.' },
{ title: 'Duty roster scheduling', body: 'Automated duty rosters and shift planning that remove manual scheduling errors.' },
{ title: 'Reporting dashboards', body: 'Real-time dashboards for district and HQ oversight, with scheduled compliance exports.' },
{ title: 'Role-based access control', body: 'Granular permissions by rank, role, and district, with full audit logging of sensitive actions.' },
];
const stats = [
{ value: '11', label: 'Districts deployed' },
{ value: '15,000+', label: 'Officers managed' },
{ value: '100,000+', label: 'Cases tracked' },
{ value: '99.9%', label: 'Uptime' },
];
// Before/after comparison stat-table: manual processes vs the digital system.
const comparison = [
{ dimension: 'FIR filing', before: 'Paper registers, no central search', after: 'Digital FIRs, searchable across districts' },
{ dimension: 'Case status', before: 'Manual file movement, opaque', after: 'Real-time tracking from registration to disposal' },
{ dimension: 'Personnel records', before: 'District-siloed paper files', after: 'Unified service records for 15,000+ officers' },
{ dimension: 'Duty rosters', before: 'Hand-written, error-prone', after: 'Automated scheduling with conflict checks' },
{ dimension: 'Oversight', before: 'Periodic manual compilation', after: 'Live dashboards for HQ and districts' },
{ dimension: 'Data consistency', before: 'Inconsistent across 11 districts', after: 'Single source of truth with audit trail' },
];
const faqs = [
{
question: 'Have you built a police management system before?',
answer:
'Yes. WorkRoot developed and deployed a Police Management System for the Bihar State Government across 11 districts. It handles personnel management, FIR digitisation, case tracking, and duty-roster scheduling for 15,000+ officers and 100,000+ cases at 99.9% uptime.',
},
{
question: 'What modules does a police management system include?',
answer:
'Core modules are FIR digitisation, case tracking, personnel management, duty-roster scheduling, real-time reporting dashboards, and role-based access control with audit logging. We tailor the module set to your jurisdictions workflow during scoping.',
},
{
question: 'How do you secure sensitive law-enforcement data?',
answer:
'Through defence-in-depth: role-based access control by rank and district, encryption at rest and in transit, immutable audit logging of every sensitive action, and OWASP-aligned security reviews. The system is built to strict security compliance for thousands of concurrent users.',
},
{
question: 'Can the system scale across many districts?',
answer:
'Yes. Our multi-district architecture uses a database-per-district model with central aggregation, so each districts data stays isolated while HQ gets a consolidated view. The live Bihar deployment already spans 11 districts on a single codebase.',
},
{
question: 'How long does deployment take?',
answer:
'Timelines depend on the number of modules and districts. We deliver a usable first module in weeks and roll out district-by-district to manage change and training. We share a detailed plan after the discovery phase.',
},
{
question: 'What technology stack do you use?',
answer:
'The police platform is built on Spring Boot for the backend, Angular for the interface, and MySQL for the data layer — chosen for security, maintainability, and performance under heavy concurrent load.',
},
];
---
<BaseLayout title={pageTitle} description={pageDescription}>
<SEO
slot="head"
type="Service"
breadcrumbs={[
{ name: 'Home', url: '/' },
{ name: 'Services', url: '/services' },
{ name: 'Police Management System', url: '/services/police-management-system' },
]}
service={{ name: serviceName, description: serviceDescription }}
faq={faqs}
/>
<!-- Hero -->
<section class="relative bg-gradient-to-br from-secondary-900 via-secondary-800 to-secondary-900 py-20 lg:py-28 overflow-hidden">
<div class="absolute inset-0 overflow-hidden pointer-events-none" aria-hidden="true">
<div class="absolute top-1/4 left-0 w-80 h-80 bg-red-500/10 rounded-full blur-3xl animate-pulse-slower"></div>
<div class="absolute bottom-0 right-1/4 w-96 h-96 bg-orange-500/10 rounded-full blur-3xl animate-pulse-slow"></div>
<div class="absolute inset-0 opacity-5" style="background-image: linear-gradient(rgba(255,255,255,.05) 1px, transparent 1px), linear-gradient(90deg, rgba(255,255,255,.05) 1px, transparent 1px); background-size: 60px 60px;"></div>
</div>
<div class="container-wrapper relative">
<div class="max-w-3xl">
<nav aria-label="Breadcrumb" class="mb-6">
<ol class="flex flex-wrap items-center gap-2 text-sm text-secondary-400">
<li><a href="/" class="hover:text-primary-300">Home</a></li>
<li aria-hidden="true">/</li>
<li><a href="/services" class="hover:text-primary-300">Services</a></li>
<li aria-hidden="true">/</li>
<li class="text-secondary-200" aria-current="page">Police Management System</li>
</ol>
</nav>
<span class="inline-flex items-center gap-2 px-4 py-1.5 mb-6 text-sm font-semibold text-red-300 bg-red-500/10 rounded-full border border-red-500/20">
<span class="w-1.5 h-1.5 rounded-full bg-red-400 animate-pulse" aria-hidden="true"></span>
Law-enforcement software
</span>
<h1 class="text-4xl sm:text-5xl lg:text-6xl font-bold text-white mb-6 leading-tight">
Police Management System
<span class="text-transparent bg-clip-text bg-gradient-to-r from-red-400 via-orange-300 to-amber-300"> proven across 11 districts.</span>
</h1>
<p class="text-lg sm:text-xl text-secondary-300 max-w-2xl mb-8 leading-relaxed">
Digitise FIRs, case tracking, personnel records, and duty rosters in one secure platform — the system we built for the
Bihar State Government already serves 15,000+ officers and tracks 100,000+ cases at 99.9% uptime.
</p>
<div class="flex flex-col sm:flex-row gap-4">
<a href="#inline-lead-form" data-cta="police-hero-primary" class="inline-flex items-center justify-center rounded-xl bg-gradient-to-r from-primary-500 to-primary-600 px-8 py-4 text-base font-bold text-white shadow-lg hover:shadow-xl hover:scale-105 transition-all focus:outline-none focus:ring-2 focus:ring-primary-400 focus:ring-offset-2 focus:ring-offset-secondary-900">
Discuss your deployment
<svg class="ml-2 w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2.5" d="M17 8l4 4m0 0l-4 4m4-4H3" /></svg>
</a>
<a href="tel:+919561417403" data-cta="police-hero-call" class="inline-flex items-center justify-center rounded-xl border-2 border-white/30 bg-white/5 px-8 py-4 text-base font-bold text-white hover:bg-white/10 transition-all focus:outline-none focus:ring-2 focus:ring-white/50">
Talk to our team
</a>
</div>
</div>
<div class="mt-14 grid grid-cols-2 sm:grid-cols-4 gap-4 lg:gap-6">
{stats.map((stat) => (
<div class="bg-white/5 backdrop-blur-sm rounded-xl p-4 lg:p-5 text-center border border-white/10">
<p class="text-2xl lg:text-3xl font-bold text-white">{stat.value}</p>
<p class="text-secondary-400 text-sm mt-1">{stat.label}</p>
</div>
))}
</div>
</div>
</section>
<!-- Overview -->
<section class="py-16 lg:py-24 bg-white dark:bg-secondary-900">
<div class="container-wrapper">
<div class="max-w-3xl mx-auto">
<span class="text-red-500 dark:text-red-400 font-semibold text-sm uppercase tracking-wider">Built for the field</span>
<h2 class="text-3xl sm:text-4xl font-bold text-secondary-900 dark:text-secondary-100 mt-2 mb-6">From paper registers to a single digital system of record</h2>
<div class="space-y-5 text-lg text-secondary-700 dark:text-secondary-300 leading-relaxed">
<p>
Before digitisation, police departments across Bihars districts relied on paper-based processes for FIR filing, case
tracking, and personnel management. The result was the familiar pattern: delays moving files between offices,
inconsistent records from one district to the next, and no way for headquarters to see the current picture without a
manual compilation. WorkRoot was engaged to replace that with one centralised, secure system.
</p>
<p>
We built a Police Management System on a <strong>Spring Boot</strong> backend and an <strong>Angular</strong>
interface, backed by <strong>MySQL</strong>, and deployed it across <strong>11 districts</strong> for the Bihar State
Government. The platform digitises First Information Reports, tracks cases through their full lifecycle, manages
personnel records and postings for <strong>15,000+ officers</strong>, and automates duty-roster scheduling. To date it
has handled <strong>100,000+ cases</strong> while sustaining 99.9% uptime under thousands of concurrent users.
</p>
<p>
Security and accountability were non-negotiable. Access is governed by role-based permissions tied to rank, role, and
district; every sensitive action is written to an immutable audit log; and data is encrypted both at rest and in
transit. The architecture keeps each districts data isolated while still rolling up a real-time view for district and
HQ leadership through reporting dashboards — so oversight is continuous rather than periodic.
</p>
<p>
The project spanned four years of delivery and iteration (20182022), and the lessons from running at that scale —
offline resilience for field-critical modules, audit logging baked into the data layer, and district-by-district
rollout to manage training and change — now inform every law-enforcement and government engagement we take on. If your
department needs to retire paper and unify records across jurisdictions, we have done exactly that in production.
</p>
</div>
</div>
</div>
</section>
<!-- Modules grid -->
<section class="py-16 lg:py-24 bg-secondary-50 dark:bg-secondary-800/50">
<div class="container-wrapper">
<div class="text-center max-w-2xl mx-auto mb-12">
<h2 class="text-3xl sm:text-4xl font-bold text-secondary-900 dark:text-secondary-100 mb-4">Core modules</h2>
<p class="text-secondary-600 dark:text-secondary-400 text-lg">Each module is running in the live Bihar deployment.</p>
</div>
<div class="grid sm:grid-cols-2 lg:grid-cols-3 gap-6">
{modules.map((m) => (
<div class="bg-white dark:bg-secondary-800 rounded-2xl p-7 border border-secondary-100 dark:border-secondary-700 shadow-sm hover:shadow-lg transition-shadow">
<div class="w-12 h-12 rounded-xl bg-red-50 dark:bg-red-900/30 text-red-600 dark:text-red-300 flex items-center justify-center mb-4" aria-hidden="true">
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M9 12l2 2 4-4m5.618-4.016A11.955 11.955 0 0112 2.944a11.955 11.955 0 01-8.618 3.04A12.02 12.02 0 003 9c0 5.591 3.824 10.29 9 11.622 5.176-1.332 9-6.03 9-11.622 0-1.042-.133-2.052-.382-3.016z" /></svg>
</div>
<h3 class="text-lg font-bold text-secondary-900 dark:text-secondary-100 mb-2">{m.title}</h3>
<p class="text-secondary-600 dark:text-secondary-400 text-sm leading-relaxed">{m.body}</p>
</div>
))}
</div>
</div>
</section>
<!-- Before/after comparison -->
<section class="py-16 lg:py-24 bg-white dark:bg-secondary-900">
<div class="container-wrapper">
<div class="max-w-4xl mx-auto">
<div class="text-center mb-10">
<h2 class="text-3xl sm:text-4xl font-bold text-secondary-900 dark:text-secondary-100 mb-4">Before and after digitisation</h2>
<p class="text-secondary-600 dark:text-secondary-400 text-lg">The operational change a unified system delivers.</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">Police operations before and after deploying the WorkRoot Police Management System</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">Area</th>
<th scope="col" class="px-5 py-4 text-sm font-bold text-secondary-500 dark:text-secondary-400">Before (manual)</th>
<th scope="col" class="px-5 py-4 text-sm font-bold text-primary-700 dark:text-primary-300">After (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.before}</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.after}
</span>
</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
</div>
</section>
<!-- Case study highlight -->
<section class="py-16 lg:py-24 bg-gradient-to-br from-secondary-900 via-secondary-800 to-secondary-900">
<div class="container-wrapper">
<div class="max-w-4xl mx-auto">
<span class="inline-flex items-center gap-2 px-4 py-1.5 mb-5 text-sm font-semibold text-red-300 bg-red-500/10 rounded-full border border-red-500/20">Case study · Government</span>
<h2 class="text-3xl sm:text-4xl font-bold text-white mb-4">Bihar State Government — Police Management System</h2>
<p class="text-lg text-secondary-300 leading-relaxed mb-8">
A four-year engagement (20182022) delivering a centralised platform across 11 districts, built to handle thousands of
concurrent users with role-based access control and strict security compliance. It remains a reference point for how we
approach multi-district public-sector software.
</p>
<div class="grid grid-cols-3 gap-4 mb-8">
{[{ v: '11', l: 'Districts' }, { v: '15,000+', l: 'Officers' }, { v: '100,000+', l: 'Cases' }].map((s) => (
<div class="bg-white/5 border border-white/10 rounded-xl p-5 text-center">
<p class="text-2xl sm:text-3xl font-bold text-white">{s.v}</p>
<p class="text-secondary-400 text-xs sm:text-sm mt-1">{s.l}</p>
</div>
))}
</div>
<div class="flex flex-wrap gap-4">
<a href="/portfolio#police-management" class="inline-flex items-center gap-2 text-primary-300 font-semibold hover:text-primary-200">
Read the full case study
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" d="M17 8l4 4m0 0l-4 4m4-4H3" /></svg>
</a>
<a href="/services/government-it-solutions-gem-vendor" class="inline-flex items-center gap-2 text-secondary-300 font-semibold hover:text-white">
See all government IT solutions
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" d="M9 5l7 7-7 7" /></svg>
</a>
</div>
</div>
</div>
</section>
<ServiceFaq
faqs={faqs}
eyebrow="For law-enforcement buyers"
heading="Police Management System — your questions answered"
subheading="Track record, modules, security, multi-district scale, and the stack we use."
/>
<GuaranteeBand />
<InlineLeadForm />
</BaseLayout>
@@ -0,0 +1,280 @@
---
// React Native App Development — keyword-focused service landing page.
// Reuses GuaranteeBand, InlineLeadForm and ServiceFaq, and feeds Service + FAQ
// schema into SEO.astro.
//
// Case-study numbers are taken verbatim from the Pophealth entry on
// /portfolio: 10,000+ sessions · 100+ providers · 4.8★ rating.
import BaseLayout from '../../layouts/BaseLayout.astro';
import SEO from '../../components/SEO.astro';
import GuaranteeBand from '../../components/GuaranteeBand.astro';
import InlineLeadForm from '../../components/InlineLeadForm.astro';
import ServiceFaq from '../../components/ServiceFaq.astro';
const pageTitle = 'React Native App Development';
const pageDescription =
'Cross-platform React Native & Expo app development — one codebase for iOS and Android, offline-first sync, push notifications, store-ready builds. WorkRoot ships apps rated 4.8★.';
const serviceName = 'React Native App Development';
const serviceDescription =
'Cross-platform mobile app development with React Native and Expo — a single codebase shipped to both the App Store and Play Store, with offline-first architecture, push notifications, and Spring Boot backend integration. Proven with a health-sessions app handling 10,000+ sessions at a 4.8★ rating.';
const capabilities = [
{ title: 'One codebase, both stores', body: 'A single React Native/Expo codebase published to iOS and Android — no separate native teams to fund.' },
{ title: 'Offline-first sync', body: 'Apps that keep working through patchy connectivity and sync cleanly when back online — proven in the field.' },
{ title: 'Push notifications', body: 'Native push on both platforms for re-engagement, reminders, and transactional alerts.' },
{ title: 'Backend integration', body: 'Secure REST integration with Spring Boot and PostgreSQL/MySQL — the same backends behind our web platforms.' },
{ title: 'Store submission', body: 'We handle App Store and Play Store builds, review, and release — not just the code.' },
{ title: 'Native-like performance', body: 'Smooth, responsive UIs that feel native, with platform-aware navigation and gestures.' },
];
const stats = [
{ value: '3+', label: 'Mobile apps shipped' },
{ value: '10,000+', label: 'Sessions handled' },
{ value: '4.8★', label: 'User rating' },
{ value: '2', label: 'Stores, 1 codebase' },
];
// Comparison: cross-platform React Native vs two separate native builds.
const comparison = [
{ dimension: 'Codebases to maintain', native: 'Two (Swift + Kotlin)', rn: 'One (React Native/Expo)' },
{ dimension: 'Time to both stores', native: 'Two parallel builds', rn: 'Single build, ship together' },
{ dimension: 'Team needed', native: 'Separate iOS + Android engineers', rn: 'One cross-platform team' },
{ dimension: 'Feature parity', native: 'Drifts between platforms', rn: 'Identical features by default' },
{ dimension: 'Ongoing cost', native: 'Two maintenance tracks', rn: 'One maintenance track' },
{ dimension: 'Offline support', native: 'Built twice', rn: 'Built once, shared' },
];
const faqs = [
{
question: 'Why choose React Native over fully native apps?',
answer:
'React Native lets you ship one codebase to both the App Store and Play Store, so you fund a single cross-platform team instead of separate iOS and Android engineers, get identical features on both platforms, and maintain one codebase. For most products it is faster and cheaper to launch and to keep running, with native-like performance.',
},
{
question: 'Do you publish to both the App Store and Play Store?',
answer:
'Yes. We handle the full release path — production builds with Expo/EAS, store metadata, review submission, and the first release — for both Apples App Store and Google Play. We can publish under your developer accounts or guide you through setting them up.',
},
{
question: 'Can the app work offline?',
answer:
'Yes. We build offline-first architecture with local storage and background sync, so the app stays usable through poor connectivity and reconciles cleanly when the connection returns. Our Pophealth health-sessions app uses exactly this approach for field use.',
},
{
question: 'How does the app connect to a backend?',
answer:
'Through secure REST APIs. We typically pair React Native apps with a Spring Boot backend and a PostgreSQL or MySQL database — the same backend stack behind our web and enterprise platforms — so your mobile and web products share one source of truth.',
},
{
question: 'How long does it take to build a React Native app?',
answer:
'A focused MVP typically ships in around 612 weeks depending on the feature set and integrations. We work in 2-week sprints with a live build available early, so you can test on real devices and steer the roadmap as it develops.',
},
{
question: 'What does a React Native app cost?',
answer:
'Mobile builds generally fall in the ₹2,00,000–₹15,00,000 range depending on scope, integrations, and whether a backend is included. Every quote is free, itemised, and fixed-price for well-defined scopes.',
},
];
---
<BaseLayout title={pageTitle} description={pageDescription}>
<SEO
slot="head"
type="Service"
breadcrumbs={[
{ name: 'Home', url: '/' },
{ name: 'Services', url: '/services' },
{ name: 'React Native App Development', url: '/services/react-native-app-development' },
]}
service={{ name: serviceName, description: serviceDescription }}
faq={faqs}
/>
<!-- Hero -->
<section class="relative bg-gradient-to-br from-secondary-900 via-secondary-800 to-secondary-900 py-20 lg:py-28 overflow-hidden">
<div class="absolute inset-0 overflow-hidden pointer-events-none" aria-hidden="true">
<div class="absolute top-1/4 left-0 w-80 h-80 bg-green-500/10 rounded-full blur-3xl animate-pulse-slower"></div>
<div class="absolute bottom-0 right-1/4 w-96 h-96 bg-emerald-500/10 rounded-full blur-3xl animate-pulse-slow"></div>
<div class="absolute inset-0 opacity-5" style="background-image: linear-gradient(rgba(255,255,255,.05) 1px, transparent 1px), linear-gradient(90deg, rgba(255,255,255,.05) 1px, transparent 1px); background-size: 60px 60px;"></div>
</div>
<div class="container-wrapper relative">
<div class="max-w-3xl">
<nav aria-label="Breadcrumb" class="mb-6">
<ol class="flex flex-wrap items-center gap-2 text-sm text-secondary-400">
<li><a href="/" class="hover:text-primary-300">Home</a></li>
<li aria-hidden="true">/</li>
<li><a href="/services" class="hover:text-primary-300">Services</a></li>
<li aria-hidden="true">/</li>
<li class="text-secondary-200" aria-current="page">React Native App Development</li>
</ol>
</nav>
<span class="inline-flex items-center gap-2 px-4 py-1.5 mb-6 text-sm font-semibold text-green-300 bg-green-500/10 rounded-full border border-green-500/20">
<span class="w-1.5 h-1.5 rounded-full bg-green-400 animate-pulse" aria-hidden="true"></span>
Cross-platform mobile
</span>
<h1 class="text-4xl sm:text-5xl lg:text-6xl font-bold text-white mb-6 leading-tight">
React Native apps for
<span class="text-transparent bg-clip-text bg-gradient-to-r from-green-400 via-emerald-300 to-teal-300"> iOS and Android — from one codebase.</span>
</h1>
<p class="text-lg sm:text-xl text-secondary-300 max-w-2xl mb-8 leading-relaxed">
Launch on both stores without the native-team overhead. We build offline-first React Native and Expo apps with push
notifications and Spring Boot backends — and ship them store-ready. Our last health app holds a 4.8★ rating across
10,000+ sessions.
</p>
<div class="flex flex-col sm:flex-row gap-4">
<a href="#inline-lead-form" data-cta="rn-hero-primary" class="inline-flex items-center justify-center rounded-xl bg-gradient-to-r from-primary-500 to-primary-600 px-8 py-4 text-base font-bold text-white shadow-lg hover:shadow-xl hover:scale-105 transition-all focus:outline-none focus:ring-2 focus:ring-primary-400 focus:ring-offset-2 focus:ring-offset-secondary-900">
Get my free app scope
<svg class="ml-2 w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2.5" d="M17 8l4 4m0 0l-4 4m4-4H3" /></svg>
</a>
<a href="tel:+919561417403" data-cta="rn-hero-call" class="inline-flex items-center justify-center rounded-xl border-2 border-white/30 bg-white/5 px-8 py-4 text-base font-bold text-white hover:bg-white/10 transition-all focus:outline-none focus:ring-2 focus:ring-white/50">
Talk to a developer
</a>
</div>
</div>
<div class="mt-14 grid grid-cols-2 sm:grid-cols-4 gap-4 lg:gap-6">
{stats.map((stat) => (
<div class="bg-white/5 backdrop-blur-sm rounded-xl p-4 lg:p-5 text-center border border-white/10">
<p class="text-2xl lg:text-3xl font-bold text-white">{stat.value}</p>
<p class="text-secondary-400 text-sm mt-1">{stat.label}</p>
</div>
))}
</div>
</div>
</section>
<!-- Overview -->
<section class="py-16 lg:py-24 bg-white dark:bg-secondary-900">
<div class="container-wrapper">
<div class="max-w-3xl mx-auto">
<span class="text-green-600 dark:text-green-400 font-semibold text-sm uppercase tracking-wider">Why cross-platform</span>
<h2 class="text-3xl sm:text-4xl font-bold text-secondary-900 dark:text-secondary-100 mt-2 mb-6">One codebase in every customers pocket</h2>
<div class="space-y-5 text-lg text-secondary-700 dark:text-secondary-300 leading-relaxed">
<p>
Building separate native apps for iOS and Android means funding two engineering teams, two release pipelines, and two
sets of bugs — and watching feature parity quietly drift apart over time. React Native collapses that into a single
codebase that ships to both the App Store and Google Play. For the vast majority of products, that means launching
sooner, spending less, and maintaining one track instead of two — without giving up the native-like feel users expect.
</p>
<p>
WorkRoot builds cross-platform apps with <strong>React Native and Expo</strong>, backed by <strong>Spring Boot</strong>
APIs and <strong>PostgreSQL</strong>. We design offline-first from the start, because real users lose signal — in
transit, in basements, in rural areas — and an app that freezes the moment connectivity drops is an app people delete.
Local storage plus background sync keeps the experience smooth and reconciles data the moment the connection returns.
</p>
<p>
Push notifications, secure authentication, deep links, and clean backend integration come standard, and we dont stop
at the code: we handle the production builds, store metadata, and review submission to get you live on both stores.
Because the backend stack matches our web and enterprise platforms, your mobile app and any web product can share one
source of truth rather than drifting into separate data silos.
</p>
<p>
The proof is in production. Our Pophealth health-sessions app — built with Expo and Spring Boot — went live for a
healthcare client, onboarded <strong>100+ providers</strong>, handled <strong>10,000+ sessions</strong> with
offline-first sync for areas of poor connectivity, and earned a <strong>4.8★</strong> user rating. If you have a mobile
product to launch, we can take it from idea to both stores.
</p>
</div>
</div>
</div>
</section>
<!-- Capabilities grid -->
<section class="py-16 lg:py-24 bg-secondary-50 dark:bg-secondary-800/50">
<div class="container-wrapper">
<div class="text-center max-w-2xl mx-auto mb-12">
<h2 class="text-3xl sm:text-4xl font-bold text-secondary-900 dark:text-secondary-100 mb-4">What we ship</h2>
<p class="text-secondary-600 dark:text-secondary-400 text-lg">Everything a production mobile app needs, on both platforms.</p>
</div>
<div class="grid sm:grid-cols-2 lg:grid-cols-3 gap-6">
{capabilities.map((c) => (
<div class="bg-white dark:bg-secondary-800 rounded-2xl p-7 border border-secondary-100 dark:border-secondary-700 shadow-sm hover:shadow-lg transition-shadow">
<div class="w-12 h-12 rounded-xl bg-green-50 dark:bg-green-900/30 text-green-600 dark:text-green-300 flex items-center justify-center mb-4" aria-hidden="true">
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M12 18h.01M8 21h8a2 2 0 002-2V5a2 2 0 00-2-2H8a2 2 0 00-2 2v14a2 2 0 002 2z" /></svg>
</div>
<h3 class="text-lg font-bold text-secondary-900 dark:text-secondary-100 mb-2">{c.title}</h3>
<p class="text-secondary-600 dark:text-secondary-400 text-sm leading-relaxed">{c.body}</p>
</div>
))}
</div>
</div>
</section>
<!-- Comparison table -->
<section class="py-16 lg:py-24 bg-white dark:bg-secondary-900">
<div class="container-wrapper">
<div class="max-w-4xl mx-auto">
<div class="text-center mb-10">
<h2 class="text-3xl sm:text-4xl font-bold text-secondary-900 dark:text-secondary-100 mb-4">React Native vs two native builds</h2>
<p class="text-secondary-600 dark:text-secondary-400 text-lg">Why one codebase usually wins.</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 separate native iOS and Android development versus cross-platform React Native</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">Two native apps</th>
<th scope="col" class="px-5 py-4 text-sm font-bold text-primary-700 dark:text-primary-300">React Native (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.native}</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.rn}
</span>
</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
</div>
</section>
<!-- Case study -->
<section class="py-16 lg:py-24 bg-gradient-to-br from-secondary-900 via-secondary-800 to-secondary-900">
<div class="container-wrapper">
<div class="max-w-4xl mx-auto">
<span class="inline-flex items-center gap-2 px-4 py-1.5 mb-5 text-sm font-semibold text-green-300 bg-green-500/10 rounded-full border border-green-500/20">Case study · Mobile</span>
<h2 class="text-3xl sm:text-4xl font-bold text-white mb-4">Pophealth — Health Sessions App</h2>
<p class="text-lg text-secondary-300 leading-relaxed mb-8">
A healthcare client needed a mobile-first tool to manage patient sessions and health data, with offline capability for
areas of poor connectivity. We built it with <strong class="text-white">Expo (React Native)</strong> and a
<strong class="text-white"> Spring Boot</strong> backend over <strong class="text-white">PostgreSQL</strong>, using an
offline-first architecture with seamless sync for session management, appointment booking, and patient tracking.
</p>
<div class="grid grid-cols-3 gap-4 mb-8">
{[{ v: '10,000+', l: 'Sessions managed' }, { v: '100+', l: 'Providers onboarded' }, { v: '4.8★', l: 'User rating' }].map((s) => (
<div class="bg-white/5 border border-white/10 rounded-xl p-5 text-center">
<p class="text-2xl sm:text-3xl font-bold text-white">{s.v}</p>
<p class="text-secondary-400 text-xs sm:text-sm mt-1">{s.l}</p>
</div>
))}
</div>
<a href="/portfolio#pophealth" class="inline-flex items-center gap-2 text-primary-300 font-semibold hover:text-primary-200">
Read the full case study
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" d="M17 8l4 4m0 0l-4 4m4-4H3" /></svg>
</a>
</div>
</div>
</section>
<ServiceFaq
faqs={faqs}
eyebrow="Before you build"
heading="React Native app development — your questions answered"
subheading="Cross-platform trade-offs, store publishing, offline support, timelines, and cost."
/>
<GuaranteeBand />
<InlineLeadForm />
</BaseLayout>
+294
View File
@@ -0,0 +1,294 @@
---
// University ERP — keyword-focused service landing page.
// Reuses GuaranteeBand, InlineLeadForm and ServiceFaq, and feeds Service + FAQ
// schema into SEO.astro.
//
// Case-study numbers are taken verbatim from the IIT Patna University
// Management System entry on /portfolio: 3,000+ students · 500+ courses ·
// 200+ faculty.
import BaseLayout from '../../layouts/BaseLayout.astro';
import SEO from '../../components/SEO.astro';
import GuaranteeBand from '../../components/GuaranteeBand.astro';
import InlineLeadForm from '../../components/InlineLeadForm.astro';
import ServiceFaq from '../../components/ServiceFaq.astro';
const pageTitle = 'University ERP & Management System';
const pageDescription =
'University ERP development — admissions, registration, examinations, grading, and faculty management in one platform. WorkRoot built the IIT Patna University Management System.';
const serviceName = 'University ERP & Management System';
const serviceDescription =
'Custom university ERP and management systems covering the full student lifecycle — admissions, course registration, examination scheduling, grade management, and faculty administration. Built on Next.js and Spring Boot, delivered for the Indian Institute of Technology, Patna.';
const modules = [
{ title: 'Admissions', body: 'Online applications, document handling, and seat allocation that replace fragmented manual intake.' },
{ title: 'Course registration', body: 'Self-service registration with prerequisite checks, capacity limits, and timetable clash detection.' },
{ title: 'Examination scheduling', body: 'Exam planning, hall and invigilator allocation, and conflict-free timetabling.' },
{ title: 'Grade management', body: 'Mark entry, moderation, GPA/CGPA computation, and transcript generation.' },
{ title: 'Faculty administration', body: 'Faculty profiles, course assignments, and workload tracking for 200+ faculty in production.' },
{ title: 'Student lifecycle', body: 'A unified record from admission to graduation, with role-based access for staff and students.' },
];
const stats = [
{ value: '3,000+', label: 'Students served' },
{ value: '500+', label: 'Courses managed' },
{ value: '200+', label: 'Faculty onboarded' },
{ value: '1', label: 'Unified platform' },
];
// Fragmented legacy vs unified university ERP comparison.
const comparison = [
{ dimension: 'Admissions', legacy: 'Separate intake tools and spreadsheets', erp: 'One online admissions pipeline' },
{ dimension: 'Registration', legacy: 'Manual forms, clash errors', erp: 'Self-service with prerequisite & clash checks' },
{ dimension: 'Examinations', legacy: 'Standalone scheduling, hall conflicts', erp: 'Conflict-free exam timetabling' },
{ dimension: 'Grading', legacy: 'Disconnected mark sheets', erp: 'Automated GPA/CGPA and transcripts' },
{ dimension: 'Faculty data', legacy: 'Siloed department records', erp: 'Central faculty administration' },
{ dimension: 'Student record', legacy: 'Scattered across systems', erp: 'Single lifecycle record of truth' },
];
const faqs = [
{
question: 'Have you built a university management system before?',
answer:
'Yes. WorkRoot built the University Management System for the Indian Institute of Technology, Patna. It manages admissions, course registration, examination scheduling, grade management, faculty profiles, and administrative workflows for 3,000+ students, 500+ courses, and 200+ faculty.',
},
{
question: 'What modules does a university ERP include?',
answer:
'A typical build covers admissions, course registration, examination scheduling, grade and transcript management, faculty administration, and the overall student lifecycle — all under role-based access for staff and students. We tailor the exact module set to your institution during scoping.',
},
{
question: 'Can it replace our existing fragmented systems?',
answer:
'Yes — that is the usual reason institutions engage us. IIT Patna needed to replace fragmented legacy systems with one unified platform, which is exactly what we delivered. We migrate existing data and consolidate intake, registration, examinations, and records into a single source of truth.',
},
{
question: 'Will it scale for our whole campus?',
answer:
'Yes. The IIT Patna platform was designed for scalability and high availability to serve the entire academic community. Our architecture handles concurrent registration and exam periods — the peak-load moments that break smaller systems.',
},
{
question: 'How long does a university ERP take to deliver?',
answer:
'We deliver module by module so value lands early — a first module in weeks, with full rollout over a few months depending on scope. Working in 2-week sprints with a live staging environment, your team sees and steers the system as it is built.',
},
{
question: 'What technology do you use for university ERPs?',
answer:
'The IIT Patna system is built on Next.js for the interface and Spring Boot for the backend, with MySQL for the data layer — chosen for scalability, maintainability, and high availability across the academic community.',
},
];
---
<BaseLayout title={pageTitle} description={pageDescription}>
<SEO
slot="head"
type="Service"
breadcrumbs={[
{ name: 'Home', url: '/' },
{ name: 'Services', url: '/services' },
{ name: 'University ERP', url: '/services/university-erp' },
]}
service={{ name: serviceName, description: serviceDescription }}
faq={faqs}
/>
<!-- Hero -->
<section class="relative bg-gradient-to-br from-secondary-900 via-secondary-800 to-secondary-900 py-20 lg:py-28 overflow-hidden">
<div class="absolute inset-0 overflow-hidden pointer-events-none" aria-hidden="true">
<div class="absolute top-1/4 left-0 w-80 h-80 bg-purple-500/10 rounded-full blur-3xl animate-pulse-slower"></div>
<div class="absolute bottom-0 right-1/4 w-96 h-96 bg-indigo-500/10 rounded-full blur-3xl animate-pulse-slow"></div>
<div class="absolute inset-0 opacity-5" style="background-image: linear-gradient(rgba(255,255,255,.05) 1px, transparent 1px), linear-gradient(90deg, rgba(255,255,255,.05) 1px, transparent 1px); background-size: 60px 60px;"></div>
</div>
<div class="container-wrapper relative">
<div class="max-w-3xl">
<nav aria-label="Breadcrumb" class="mb-6">
<ol class="flex flex-wrap items-center gap-2 text-sm text-secondary-400">
<li><a href="/" class="hover:text-primary-300">Home</a></li>
<li aria-hidden="true">/</li>
<li><a href="/services" class="hover:text-primary-300">Services</a></li>
<li aria-hidden="true">/</li>
<li class="text-secondary-200" aria-current="page">University ERP</li>
</ol>
</nav>
<span class="inline-flex items-center gap-2 px-4 py-1.5 mb-6 text-sm font-semibold text-purple-300 bg-purple-500/10 rounded-full border border-purple-500/20">
<span class="w-1.5 h-1.5 rounded-full bg-purple-400 animate-pulse" aria-hidden="true"></span>
Higher-education software
</span>
<h1 class="text-4xl sm:text-5xl lg:text-6xl font-bold text-white mb-6 leading-tight">
University ERP that runs
<span class="text-transparent bg-clip-text bg-gradient-to-r from-purple-400 via-indigo-300 to-primary-300"> the whole student lifecycle.</span>
</h1>
<p class="text-lg sm:text-xl text-secondary-300 max-w-2xl mb-8 leading-relaxed">
Admissions, registration, examinations, grading, and faculty — one platform instead of a dozen disconnected tools.
We built exactly this for the Indian Institute of Technology, Patna, on Next.js and Spring Boot.
</p>
<div class="flex flex-col sm:flex-row gap-4">
<a href="#inline-lead-form" data-cta="uni-hero-primary" class="inline-flex items-center justify-center rounded-xl bg-gradient-to-r from-primary-500 to-primary-600 px-8 py-4 text-base font-bold text-white shadow-lg hover:shadow-xl hover:scale-105 transition-all focus:outline-none focus:ring-2 focus:ring-primary-400 focus:ring-offset-2 focus:ring-offset-secondary-900">
Scope my university ERP
<svg class="ml-2 w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2.5" d="M17 8l4 4m0 0l-4 4m4-4H3" /></svg>
</a>
<a href="tel:+919561417403" data-cta="uni-hero-call" class="inline-flex items-center justify-center rounded-xl border-2 border-white/30 bg-white/5 px-8 py-4 text-base font-bold text-white hover:bg-white/10 transition-all focus:outline-none focus:ring-2 focus:ring-white/50">
Talk to our team
</a>
</div>
</div>
<div class="mt-14 grid grid-cols-2 sm:grid-cols-4 gap-4 lg:gap-6">
{stats.map((stat) => (
<div class="bg-white/5 backdrop-blur-sm rounded-xl p-4 lg:p-5 text-center border border-white/10">
<p class="text-2xl lg:text-3xl font-bold text-white">{stat.value}</p>
<p class="text-secondary-400 text-sm mt-1">{stat.label}</p>
</div>
))}
</div>
</div>
</section>
<!-- Overview -->
<section class="py-16 lg:py-24 bg-white dark:bg-secondary-900">
<div class="container-wrapper">
<div class="max-w-3xl mx-auto">
<span class="text-purple-600 dark:text-purple-400 font-semibold text-sm uppercase tracking-wider">Built for institutions</span>
<h2 class="text-3xl sm:text-4xl font-bold text-secondary-900 dark:text-secondary-100 mt-2 mb-6">One platform for admissions through graduation</h2>
<div class="space-y-5 text-lg text-secondary-700 dark:text-secondary-300 leading-relaxed">
<p>
Universities accumulate software the way they accumulate buildings — one tool for admissions, another for
registration, a spreadsheet for grades, a separate system for faculty. Each works in isolation, but the seams show at
exactly the wrong moments: registration week, exam season, results day. A university ERP replaces that patchwork with a
single platform that follows each student from application to graduation, and gives staff one place to work.
</p>
<p>
WorkRoot delivered precisely this for the <strong>Indian Institute of Technology, Patna</strong>. The institution
needed to replace fragmented legacy systems with a unified platform spanning admissions, course registration,
examination scheduling, grade management, faculty profiles, and administrative workflows. We built it on a
<strong> Next.js</strong> front end with a <strong>Spring Boot</strong> backend and a <strong>MySQL</strong> data
layer, engineered for scalability and high availability so it holds up when the whole campus logs in at once. In
production it serves <strong>3,000+ students</strong>, <strong>500+ courses</strong>, and <strong>200+ faculty</strong>.
</p>
<p>
The hard part of academic software is the peak. Registration and examinations concentrate enormous load into a few
days, and the modules have to coordinate — prerequisites gate registration, registration drives exam scheduling,
exams feed grading, grades roll into transcripts. We model those dependencies explicitly so the system stays
conflict-free under pressure: capacity limits and clash detection at registration, hall and invigilator allocation at
exam time, and automated GPA/CGPA computation and transcript generation at results.
</p>
<p>
Role-based access keeps students, faculty, and administrators each seeing only what they should, and the unified
student record becomes a single source of truth that ends the reconciliation scramble between departments. The
platform is yours on delivery — full source code, documentation, and deployment access — so your institution stays in
control of its most important data. If you are ready to retire a fragmented stack, we have built the replacement
before, at an IIT.
</p>
</div>
</div>
</div>
</section>
<!-- Modules grid -->
<section class="py-16 lg:py-24 bg-secondary-50 dark:bg-secondary-800/50">
<div class="container-wrapper">
<div class="text-center max-w-2xl mx-auto mb-12">
<h2 class="text-3xl sm:text-4xl font-bold text-secondary-900 dark:text-secondary-100 mb-4">University ERP modules</h2>
<p class="text-secondary-600 dark:text-secondary-400 text-lg">The full academic lifecycle, in one connected system.</p>
</div>
<div class="grid sm:grid-cols-2 lg:grid-cols-3 gap-6">
{modules.map((m) => (
<div class="bg-white dark:bg-secondary-800 rounded-2xl p-7 border border-secondary-100 dark:border-secondary-700 shadow-sm hover:shadow-lg transition-shadow">
<div class="w-12 h-12 rounded-xl bg-purple-50 dark:bg-purple-900/30 text-purple-600 dark:text-purple-300 flex items-center justify-center mb-4" aria-hidden="true">
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M12 14l9-5-9-5-9 5 9 5z" /><path stroke-linecap="round" stroke-linejoin="round" d="M12 14l6.16-3.422A12.083 12.083 0 0112 21.5a12.083 12.083 0 01-6.16-10.922L12 14z" /></svg>
</div>
<h3 class="text-lg font-bold text-secondary-900 dark:text-secondary-100 mb-2">{m.title}</h3>
<p class="text-secondary-600 dark:text-secondary-400 text-sm leading-relaxed">{m.body}</p>
</div>
))}
</div>
</div>
</section>
<!-- Comparison table -->
<section class="py-16 lg:py-24 bg-white dark:bg-secondary-900">
<div class="container-wrapper">
<div class="max-w-4xl mx-auto">
<div class="text-center mb-10">
<h2 class="text-3xl sm:text-4xl font-bold text-secondary-900 dark:text-secondary-100 mb-4">Fragmented tools vs a unified ERP</h2>
<p class="text-secondary-600 dark:text-secondary-400 text-lg">What changes when everything connects.</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 fragmented academic tools versus a unified WorkRoot university ERP</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">Area</th>
<th scope="col" class="px-5 py-4 text-sm font-bold text-secondary-500 dark:text-secondary-400">Fragmented tools</th>
<th scope="col" class="px-5 py-4 text-sm font-bold text-primary-700 dark:text-primary-300">Unified ERP (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.legacy}</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.erp}
</span>
</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
</div>
</section>
<!-- Case study -->
<section class="py-16 lg:py-24 bg-gradient-to-br from-secondary-900 via-secondary-800 to-secondary-900">
<div class="container-wrapper">
<div class="max-w-4xl mx-auto">
<span class="inline-flex items-center gap-2 px-4 py-1.5 mb-5 text-sm font-semibold text-purple-300 bg-purple-500/10 rounded-full border border-purple-500/20">Case study · Higher education</span>
<h2 class="text-3xl sm:text-4xl font-bold text-white mb-4">University Management System — IIT Patna</h2>
<p class="text-lg text-secondary-300 leading-relaxed mb-8">
An end-to-end University Management System for the Indian Institute of Technology, Patna — managing student admissions,
course registration, examination scheduling, grade management, faculty profiles, and administrative workflows in one
scalable, high-availability platform.
</p>
<div class="grid grid-cols-3 gap-4 mb-8">
{[{ v: '3,000+', l: 'Students served' }, { v: '500+', l: 'Courses managed' }, { v: '200+', l: 'Faculty onboarded' }].map((s) => (
<div class="bg-white/5 border border-white/10 rounded-xl p-5 text-center">
<p class="text-2xl sm:text-3xl font-bold text-white">{s.v}</p>
<p class="text-secondary-400 text-xs sm:text-sm mt-1">{s.l}</p>
</div>
))}
</div>
<blockquote class="border-l-4 border-primary-500 pl-5 text-secondary-200 italic leading-relaxed">
“WorkRoot delivered our University Management System for IIT Patna on time and within budget. The Next.js + Spring Boot
architecture is robust and scalable. They are a reliable technology partner for complex enterprise projects.”
<footer class="mt-3 not-italic text-sm text-secondary-400">— Dilip Raj, Director, MOC Soft Technologies Pvt Ltd</footer>
</blockquote>
<div class="mt-8 flex flex-wrap gap-4">
<a href="/portfolio#university-management-iitp" class="inline-flex items-center gap-2 text-primary-300 font-semibold hover:text-primary-200">
Read the full case study
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" d="M17 8l4 4m0 0l-4 4m4-4H3" /></svg>
</a>
<a href="/services/custom-erp-development" class="inline-flex items-center gap-2 text-secondary-300 font-semibold hover:text-white">
Explore custom ERP development
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" d="M9 5l7 7-7 7" /></svg>
</a>
</div>
</div>
</div>
</section>
<ServiceFaq
faqs={faqs}
eyebrow="For institutions"
heading="University ERP — your questions answered"
subheading="Track record, modules, migration, scale, timelines, and the stack we use."
/>
<GuaranteeBand />
<InlineLeadForm />
</BaseLayout>
+16 -4
View File
@@ -22,10 +22,22 @@ const sitemapSections = [
title: 'Services', title: 'Services',
icon: 'briefcase', icon: 'briefcase',
links: [ links: [
{ name: 'All Services', url: '/services', description: 'Our full range of IT service offerings' },
{ name: 'Web Development', url: '/services#web-development', description: 'Custom web applications and e-commerce' }, { name: 'Web Development', url: '/services#web-development', description: 'Custom web applications and e-commerce' },
{ name: 'Mobile Development', url: '/services#mobile-development', description: 'iOS, Android, and cross-platform apps' }, { name: 'Mobile Development', url: '/services#mobile-development', description: 'iOS, Android, and cross-platform apps' },
{ name: 'AI & Machine Learning', url: '/services#ai-ml', description: 'Intelligent solutions and automation' }, { name: 'UI/UX Design', url: '/services#ui-ux-design', description: 'Accessible, conversion-focused interfaces' },
{ name: 'Cloud Services', url: '/services#cloud', description: 'Cloud migration and infrastructure' }, { name: 'IT Consulting & DevOps', url: '/services#it-consulting', description: 'Architecture, cloud, and CI/CD' },
],
},
{
title: 'Solutions',
icon: 'briefcase',
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: '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' },
], ],
}, },
{ {
@@ -78,8 +90,8 @@ const icons: Record<string, string> = {
--- ---
<BaseLayout <BaseLayout
title="Sitemap" title="Sitemap — Browse All Pages and Resources"
description="WorkRoot IT Solutions Sitemap - Navigate to all pages and resources on our website." description="Browse every page on WorkRoot IT Solutions in one place — services, portfolio, blog, about, contact, and our web, mobile, ERP, and government IT resources."
> >
<!-- Hero Section --> <!-- Hero Section -->
<section class="relative bg-gradient-to-br from-secondary-900 via-secondary-800 to-secondary-900 py-16 lg:py-20"> <section class="relative bg-gradient-to-br from-secondary-900 via-secondary-800 to-secondary-900 py-16 lg:py-20">
+6
View File
@@ -25,6 +25,12 @@ export const GET: APIRoute = async () => {
const staticPages: SitemapEntry[] = [ const staticPages: SitemapEntry[] = [
{ url: '/', changefreq: 'weekly', priority: 1.0 }, { url: '/', changefreq: 'weekly', priority: 1.0 },
{ url: '/services/', changefreq: 'monthly', priority: 0.9 }, { url: '/services/', changefreq: 'monthly', priority: 0.9 },
// Keyword-focused service landing pages
{ url: '/services/custom-erp-development/', changefreq: 'monthly', priority: 0.8 },
{ url: '/services/government-it-solutions-gem-vendor/', changefreq: 'monthly', priority: 0.8 },
{ url: '/services/police-management-system/', changefreq: 'monthly', priority: 0.8 },
{ url: '/services/react-native-app-development/', changefreq: 'monthly', priority: 0.8 },
{ url: '/services/university-erp/', changefreq: 'monthly', priority: 0.8 },
{ url: '/portfolio/', changefreq: 'weekly', priority: 0.8 }, { url: '/portfolio/', changefreq: 'weekly', priority: 0.8 },
{ url: '/about/', changefreq: 'monthly', priority: 0.8 }, { url: '/about/', changefreq: 'monthly', priority: 0.8 },
{ url: '/blog/', changefreq: 'weekly', priority: 0.8 }, { url: '/blog/', changefreq: 'weekly', priority: 0.8 },
+2 -2
View File
@@ -8,8 +8,8 @@ const lastUpdated = 'March 20, 2026';
--- ---
<BaseLayout <BaseLayout
title="Terms of Service" title="Terms of Service — Website & Project Terms"
description="WorkRoot IT Solutions Terms of Service - Read our terms and conditions for using our website and services." description="Read the WorkRoot IT Solutions terms of service covering acceptable use of our website, project engagements, intellectual property, liability, and warranties."
noIndex={true} noIndex={true}
> >
<!-- Hero Section --> <!-- Hero Section -->
+2 -2
View File
@@ -84,7 +84,7 @@ export function generateArticleSchema(
}, },
publisher: { publisher: {
'@type': 'Organization', '@type': 'Organization',
name: 'WorkRoot IT Solutions', name: 'WorkRoot IT Solutions LLP',
logo: { logo: {
'@type': 'ImageObject', '@type': 'ImageObject',
url: 'https://workroot.in/logo.png', url: 'https://workroot.in/logo.png',
@@ -132,7 +132,7 @@ export function generateServiceSchema(
description: description, description: description,
provider: { provider: {
'@type': 'Organization', '@type': 'Organization',
name: 'WorkRoot IT Solutions', name: 'WorkRoot IT Solutions LLP',
url: siteUrl, url: siteUrl,
}, },
areaServed: { areaServed: {
+32 -14
View File
@@ -6,7 +6,10 @@ import { test, expect } from '@playwright/test';
* Covers: * Covers:
* - The form is mounted on the homepage between FAQ/tech-stack and the * - The form is mounted on the homepage between FAQ/tech-stack and the
* final CTA band. * final CTA band.
* - Filling all three fields and submitting calls /api/contact and shows * - It exposes the compact 5-field layout (name, email, phone, project type,
* message) with the correct mobile keyboard hints per field. Only name,
* email and project type are required; phone and message are optional.
* - Filling the required fields and submitting calls /api/contact and shows
* an in-place thank-you state without a full-page navigation. * an in-place thank-you state without a full-page navigation.
* - The submission is tagged `source: 'homepage-inline'` so leads can be * - The submission is tagged `source: 'homepage-inline'` so leads can be
* segmented downstream. * segmented downstream.
@@ -15,7 +18,7 @@ import { test, expect } from '@playwright/test';
*/ */
test.describe('Inline Lead Form (homepage)', () => { test.describe('Inline Lead Form (homepage)', () => {
test('form is visible on the homepage and has the three required fields', async ({ page }) => { test('form is visible on the homepage with the compact field set and mobile keyboards', async ({ page }) => {
await page.goto('/'); await page.goto('/');
const form = page.getByTestId('inline-lead-form'); const form = page.getByTestId('inline-lead-form');
@@ -23,8 +26,10 @@ test.describe('Inline Lead Form (homepage)', () => {
await expect(form).toBeVisible(); await expect(form).toBeVisible();
await expect(page.locator('#inline-lead-name')).toBeVisible(); await expect(page.locator('#inline-lead-name')).toBeVisible();
await expect(page.locator('#inline-lead-contact')).toBeVisible(); await expect(page.locator('#inline-lead-email')).toBeVisible();
await expect(page.locator('#inline-lead-phone')).toBeVisible();
await expect(page.locator('#inline-lead-project-type')).toBeVisible(); await expect(page.locator('#inline-lead-project-type')).toBeVisible();
await expect(page.locator('#inline-lead-message')).toBeVisible();
// Touch-target sanity: the submit button must be at least 48px tall. // Touch-target sanity: the submit button must be at least 48px tall.
const submit = page.locator('#inline-lead-submit'); const submit = page.locator('#inline-lead-submit');
@@ -32,9 +37,17 @@ test.describe('Inline Lead Form (homepage)', () => {
expect(submitBox).not.toBeNull(); expect(submitBox).not.toBeNull();
expect(submitBox!.height).toBeGreaterThanOrEqual(48); expect(submitBox!.height).toBeGreaterThanOrEqual(48);
// Correct mobile-friendly attributes on the contact field. // Correct mobile keyboard per field.
await expect(page.locator('#inline-lead-contact')).toHaveAttribute('autocomplete', 'email tel'); await expect(page.locator('#inline-lead-email')).toHaveAttribute('type', 'email');
await expect(page.locator('#inline-lead-contact')).toHaveAttribute('inputmode', 'email'); await expect(page.locator('#inline-lead-email')).toHaveAttribute('inputmode', 'email');
await expect(page.locator('#inline-lead-email')).toHaveAttribute('autocomplete', 'email');
await expect(page.locator('#inline-lead-phone')).toHaveAttribute('type', 'tel');
await expect(page.locator('#inline-lead-phone')).toHaveAttribute('inputmode', 'tel');
await expect(page.locator('#inline-lead-phone')).toHaveAttribute('autocomplete', 'tel');
// Phone + message are optional (no `required` attribute).
await expect(page.locator('#inline-lead-phone')).not.toHaveAttribute('required', /.*/);
await expect(page.locator('#inline-lead-message')).not.toHaveAttribute('required', /.*/);
}); });
test('blocks submit when required fields are empty', async ({ page }) => { test('blocks submit when required fields are empty', async ({ page }) => {
@@ -57,12 +70,12 @@ test.describe('Inline Lead Form (homepage)', () => {
// The blank-field errors should surface and the network should NOT be hit. // The blank-field errors should surface and the network should NOT be hit.
await expect(page.locator('#inline-lead-name-error')).toBeVisible(); await expect(page.locator('#inline-lead-name-error')).toBeVisible();
await expect(page.locator('#inline-lead-contact-error')).toBeVisible(); await expect(page.locator('#inline-lead-email-error')).toBeVisible();
await expect(page.locator('#inline-lead-project-type-error')).toBeVisible(); await expect(page.locator('#inline-lead-project-type-error')).toBeVisible();
expect(apiCalled).toBe(false); expect(apiCalled).toBe(false);
}); });
test('fills and submits the form successfully with an email contact', async ({ page }) => { test('fills the required fields and submits successfully', async ({ page }) => {
await page.goto('/'); await page.goto('/');
const form = page.getByTestId('inline-lead-form'); const form = page.getByTestId('inline-lead-form');
await form.scrollIntoViewIfNeeded(); await form.scrollIntoViewIfNeeded();
@@ -85,7 +98,7 @@ test.describe('Inline Lead Form (homepage)', () => {
}); });
await page.locator('#inline-lead-name').fill('Inline Lead Tester'); await page.locator('#inline-lead-name').fill('Inline Lead Tester');
await page.locator('#inline-lead-contact').fill('inline-lead@example.com'); await page.locator('#inline-lead-email').fill('inline-lead@example.com');
await page.locator('#inline-lead-project-type').selectOption('Web App'); await page.locator('#inline-lead-project-type').selectOption('Web App');
await page.locator('#inline-lead-submit').click(); await page.locator('#inline-lead-submit').click();
@@ -103,11 +116,14 @@ test.describe('Inline Lead Form (homepage)', () => {
expect(captured.name).toBe('Inline Lead Tester'); expect(captured.name).toBe('Inline Lead Tester');
expect(captured.email).toBe('inline-lead@example.com'); expect(captured.email).toBe('inline-lead@example.com');
expect(captured.subject).toBe('web-development'); expect(captured.subject).toBe('web-development');
// Message is synthesized when left blank so the backend's >=10 char rule passes.
expect(typeof captured.message).toBe('string');
expect(captured.message.length).toBeGreaterThanOrEqual(10);
// Honeypot must be empty for a real user submission. // Honeypot must be empty for a real user submission.
expect(captured.website ?? '').toBe(''); expect(captured.website ?? '').toBe('');
}); });
test('routes a phone-shaped contact value into the phone field', async ({ page }) => { test('sends the phone and a typed message through their own fields', async ({ page }) => {
await page.goto('/'); await page.goto('/');
const form = page.getByTestId('inline-lead-form'); const form = page.getByTestId('inline-lead-form');
await form.scrollIntoViewIfNeeded(); await form.scrollIntoViewIfNeeded();
@@ -127,17 +143,19 @@ test.describe('Inline Lead Form (homepage)', () => {
}); });
await page.locator('#inline-lead-name').fill('Phone Lead'); await page.locator('#inline-lead-name').fill('Phone Lead');
await page.locator('#inline-lead-contact').fill('+91 95614 17403'); await page.locator('#inline-lead-email').fill('phone-lead@example.com');
await page.locator('#inline-lead-phone').fill('+91 95614 17403');
await page.locator('#inline-lead-project-type').selectOption('Government / GeM'); await page.locator('#inline-lead-project-type').selectOption('Government / GeM');
await page.locator('#inline-lead-message').fill('We need a GeM-ready procurement portal.');
await page.locator('#inline-lead-submit').click(); await page.locator('#inline-lead-submit').click();
await expect(page.getByTestId('inline-lead-success')).toBeVisible(); await expect(page.getByTestId('inline-lead-success')).toBeVisible();
expect(captured).toBeTruthy(); expect(captured).toBeTruthy();
// The real email and phone are sent as-is in their own fields.
expect(captured.email).toBe('phone-lead@example.com');
expect(captured.phone).toBe('+91 95614 17403'); expect(captured.phone).toBe('+91 95614 17403');
// The client synthesizes a placeholder email so the backend's strict email expect(captured.message).toBe('We need a GeM-ready procurement portal.');
// validator accepts phone-only contacts. The real phone is what matters.
expect(captured.email).toMatch(/^lead-\d+@homepage\.workroot\.in$/);
expect(captured.subject).toBe('government-systems'); expect(captured.subject).toBe('government-systems');
expect(captured.source).toBe('homepage-inline'); expect(captured.source).toBe('homepage-inline');
}); });
Binary file not shown.

Before

Width:  |  Height:  |  Size: 814 KiB

After

Width:  |  Height:  |  Size: 1.5 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

After

Width:  |  Height:  |  Size: 512 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 176 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

After

Width:  |  Height:  |  Size: 455 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 124 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

After

Width:  |  Height:  |  Size: 384 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 157 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

After

Width:  |  Height:  |  Size: 506 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 161 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

After

Width:  |  Height:  |  Size: 401 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 161 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

After

Width:  |  Height:  |  Size: 402 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 158 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 291 KiB