I Finished What I Started: Adding AI to Every Layer of a…
    Neura MarketNeura Market/Midjourney
    ChatGPTChatGPTClaudeClaudeGeminiGeminiCursorCursorGrokGrokPerplexityPerplexityMidjourneyMidjourney
    DeepSeekDeepSeekCoPilotCoPilotStable DiffusionStable Diffusion
    View All Directories
    OverviewPromptsBlogVideosGuidesCoursesCommunityStylesTrending
    MidjourneyBlogI Finished What I Started: Adding AI to Every Layer of a Form Builder (With GitHub Copilot)
    Back to Blog
    I Finished What I Started: Adding AI to Every Layer of a Form Builder (With GitHub Copilot)
    devchallenge

    I Finished What I Started: Adding AI to Every Layer of a Form Builder (With GitHub Copilot)

    Natheesh Kumar June 4, 2026
    0 views

    This is a submission for the GitHub Finish-Up-A-Thon Challenge What I Built Dculus...


    title: I Finished What I Started: Adding AI to Every Layer of a Form Builder (With GitHub Copilot) published: true tags: devchallenge, githubchallenge, githubcopilot, webdev

    This is a submission for the GitHub Finish-Up-A-Thon Challenge


    What I Built

    Dculus Forms is a full-stack, multi-tenant SaaS form builder — think Typeform, but with real-time collaborative editing (Y.js), a plugin system, analytics, and multi-cloud deployment.

    I started this project with the core builder working: drag-and-drop fields, a public form viewer, GraphQL API, Prisma + PostgreSQL. But several important layers were stubbed out or incomplete:

    • The AI form builder chat existed but had stability problems: schema fetched fresh on every message, unbounded history, no user feedback while the AI was thinking, and no way to undo a bad AI edit.
    • The analytics dashboard showed numbers but gave users no guidance on what to do about them.
    • Dashboard stats had three hardcoded trend badges (+12%, +8%, +15%) that were never connected to real data.
    • The settings page was a double-nested tab maze.

    The GitHub Finish-Up-A-Thon gave me the forcing function to finally ship these. Here's exactly what the before/after looks like — and how GitHub Copilot helped me get there faster than I thought possible.


    Image description

    The "Before" State

    AI Chat: Working but Fragile

    The form builder had an AIEditDrawer — a chat panel where users could type "add a required email field after Full Name" and the AI would apply it. Under the hood: a Vercel AI SDK ToolLoopAgent with 11 tools streaming over HTTP to a useChat hook.

    Problems I'd deferred:

    • Schema re-fetched from Y.js on every single message — every turn read the full collaborative document regardless of whether anything had changed.
    • Conversation history grew unbounded — a long session could eat thousands of tokens just in history replay.
    • Users saw generic bouncing dots — no indication of what tool the AI was calling, what it changed, or how many steps it had taken.
    • No undo for AI turns — if the AI made a bad batch edit, users had no targeted recovery path.
    • Destructive actions applied immediately — removeFields went straight to the Y.js store with no warning, no "this affects 47 existing responses" heads-up.
    • Token quota was invisible — users on the free plan (200k tokens/month) had no idea how close to the limit they were.

    Analytics: Data Without Direction

    The field analytics tab showed per-field fill rates, option frequency charts, average response lengths, and completion time percentiles. Rich data — but purely descriptive. A user seeing "your Country dropdown has a 31% fill rate" had to mentally translate that into action themselves.

    Image description

    Dashboard Stats: Hardcoded Lies

    // Before — StatsGrid.tsx
    <TrendBadge value={positiveTrend(12)} />  // always +12%
    <TrendBadge value={positiveTrend(8)} />   // always +8%
    <TrendBadge value={positiveTrend(15)} />  // always +15%
    

    These weren't placeholders with a TODO comment. They had shipped and sat there for months.

    Settings: Double-Nested Tab Maze

    The Settings page used tabs within tabs — you'd click Account, then click a sub-tab for Profile, then another for Organization. Three levels of navigation for simple tasks.


    How GitHub Copilot Helped Me Get There

    Before writing any feature code, I set up GitHub Copilot with domain-specific instruction files. This turned out to be the highest-leverage thing I did in the entire sprint.

    The Instruction Files

    I created 8 instruction files in .github/instructions/, each scoped to a part of the codebase:

    .github/instructions/
    ├── authentication.instructions.md   # better-auth patterns
    ├── backend.instructions.md          # resolver → service → repository layering
    ├── database.instructions.md         # Prisma conventions, serialization rules
    ├── frontend.instructions.md         # import rules, component organization
    ├── graphql.instructions.md          # schema-first conventions, error codes
    ├── i18n.instructions.md             # translation requirements (en + ta)
    ├── shared-packages.instructions.md  # @dculus/ui, @dculus/types, @dculus/utils
    └── testing.instructions.md          # Vitest, Cucumber BDD patterns
    

    Each file has an applyTo frontmatter that scopes it automatically. For example:

    ---
    applyTo: "apps/form-app/**,apps/form-viewer/**,apps/admin-app/**"
    ---
    

    What this meant in practice: When I asked Copilot to "add a GraphQL query for field insights," it automatically:

    • Used createGraphQLError with typed GRAPHQL_ERROR_CODES (not throw new Error())
    • Called requireAuth + requireOrganizationMembership in the resolver
    • Used @dculus/types for type imports, not local relative paths
    • Added Tamil translations alongside English ones

    Without the instruction files, I'd have been correcting import paths and missing auth guards on every single file. With them, the first draft was almost always structurally correct.

    Custom Agents for Complex Work

    For multi-file features, I used Copilot's custom agent mode with specialized agents I'd defined:

    • feature-developer — full-stack features across GraphQL schema, resolver, service, and frontend
    • field-type-developer — covers all 10 layers a new field type touches (types, validation, renderer, builder UI, analytics, filters, export, collaboration, i18n)

    When I was building AI Field Insights (a feature that spans Prisma schema, a new backend service, 2 GraphQL resolver additions, 5 frontend components, and translations), I used the feature-developer agent with a detailed spec. Copilot produced a complete plan with a file map and step-by-step tasks, each with failing tests first. I reviewed, accepted the plan, and executed it task by task.

    The back-and-forth that would have taken me a day of context-switching took a focused afternoon.


    What I Finished

    1. AI Chat — Stability + Full Feature Overhaul

    Schema cache with 10-second TTL: Instead of reading the Y.js document on every message, the route now caches the serialized form schema per formId. A /api/ai/invalidate-schema endpoint is called by applyAIOp whenever a mutation is applied — so the cache is always fresh when the next message arrives.

    // After — aiChat.ts (simplified)
    const schemaCache = new Map<string, { schema: FormSchema; cachedAt: number }>();
    const SCHEMA_CACHE_TTL_MS = 10_000;
    
    export function getFormSchema(formId: string): FormSchema | null {
      const entry = schemaCache.get(formId);
      if (!entry) return null;
      if (Date.now() - entry.cachedAt > SCHEMA_CACHE_TTL_MS) {
        schemaCache.delete(formId);
        return null;
      }
      return entry.schema;
    }
    

    20-message history cap:

    // After — aiChatService.ts
    const MAX_HISTORY_MESSAGES = 20;
    const rows = await prisma.aIChatMessage.findMany({
      where: { conversationId },
      orderBy: { createdAt: 'desc' },
      take: MAX_HISTORY_MESSAGES,
    });
    return rows.reverse();
    

    Token meter — users can now see their quota:

    // AITokenMeter.tsx — rendered at the bottom of every AIEditDrawer
    <AITokenMeter organizationId={organizationId} />
    // Shows: [████████░░] 1.4M / 2M tokens used (Starter plan)
    

    ChangeSummaryCard — what actually changed this turn: Every AI response now includes a structured diff card showing added/modified/removed fields, so users don't have to visually scan the entire form to see what the AI did.

    Propose-then-confirm for destructive operations: removeFields, removePage, and proposeFieldTypeChange no longer apply immediately. They return a proposal card showing exactly how many existing responses would be affected:

    ⚠️  Remove "Country" (select)?
        This field has 312 existing responses.
        [Cancel]  [Confirm Delete]
    

    Field type conversion done right: Converting a text field to a select isn't a mutation — it's delete-old + create-new with a new ID. This preserves response history integrity (old responses keep their text values; the new field starts clean). Copilot caught this edge case when I described the feature: it flagged that mutating type in place would corrupt the data: { [fieldId]: value } structure that analytics and exports rely on.

    Context-aware suggestion chips (useAIChips): The chat drawer now shows smart chips based on the current state — "Add validation", "Reorder fields", "Add a page" — derived from the form's actual content, not hardcoded strings.

    Net result: 14 tools total (up from 11), all with TypeScript Zod schemas validated at runtime, streamed via DefaultChatTransport.


    2. AI Field Insights — Analytics That Explains Itself

    This is the feature I'm most proud of, because it closes the loop between seeing a problem and fixing it without leaving the page.

    How it works:

    1. User opens Form Analytics → Field Analytics.
    2. No tips yet — a "✨ Analyze all fields" button appears.
    3. One click triggers a single batch AI call (1–3k tokens). The AI receives all field analytics data and returns a structured insight per field.
    4. Tips are persisted in a new AIFieldInsight Prisma model, keyed by (formId, fieldId) with a schemaHash for staleness detection.
    5. Future visits read from the database — zero token cost until the form schema changes.
    model AIFieldInsight {
      id           String   @id @default(cuid())
      formId       String
      fieldId      String
      tip          String   @db.Text      // "67% of users skip this field. Consider making it optional."
      fixPrompt    String   @db.Text      // exact message to pre-fill in AIEditDrawer
      severity     String                 // "warning" | "error" | "success" | "info"
      schemaHash   String                 // 16-char SHA-256 prefix — staleness detection
      generatedAt  DateTime @default(now())
    
      form Form @relation(fields: [formId], references: [id], onDelete: Cascade)
    
      @@unique([formId, fieldId])
      @@index([formId])
    }
    

    The "Fix with AI ✦" button: Each insight card has a button that opens the AIEditDrawer with the fixPrompt pre-loaded as the initial message. The user clicks confirm, and the AI applies the suggested fix immediately. The gap between insight and action is one click.

    Stale detection: If the form schema changes after analysis, a yellow banner appears: "Form changed since last analysis — Re-analyze?" This is powered by comparing a 16-char SHA-256 prefix of the field structure against the stored schemaHash.


    3. Real Trend Percentages — Goodbye Hardcoded Lies

    Before:

    <TrendBadge value={positiveTrend(12)} />  // +12% — fiction
    <TrendBadge value={positiveTrend(8)} />   // +8%  — fiction
    <TrendBadge value={positiveTrend(15)} />  // +15% — fiction
    

    After — GraphQL schema:

    type FormDashboardStats {
      responsesToday: Int!
      responsesThisWeek: Int!
      responsesThisMonth: Int!
      trendResponsesToday: Float   # % change today vs yesterday; null = no data
      trendThisWeek: Float         # % change this week vs last week; null = no data
      trendResponseRate: Float     # percentage-point delta; null = <10 views
    }
    

    The resolver runs 4 parallel Prisma queries and computes real period-over-period deltas. null means insufficient data — the badge simply hides instead of showing a misleading number. A form with 3 total responses doesn't pretend it grew by 12%.


    4. Settings Redesign — Typeform-Style Sidebar

    Replaced double-nested tabs with a clean left sidebar following a pattern users already know from tools like Typeform and Linear:

    Settings
    ├── Account
    │   └── Profile          ← name, email, avatar upload
    └── Organization
        ├── Members           ← invite, role management
        └── Billing & Plans   ← Chargebee subscription dashboard
    

    Old URLs (/settings/account, /settings/team) redirect to new equivalents — no broken links for existing users.


    The Copilot Moment That Surprised Me Most

    When building the field type conversion feature, I described the problem to Copilot: "convert a text field to a select field." Its first response wasn't code — it was a question about data integrity:

    "If you mutate the type in place and keep the same field ID, responses stored as data: { [fieldId]: "some text" } will be read by the select renderer expecting an option value. Do you want conversion to create a new field ID so old responses are preserved as immutable history?"

    That's the kind of thing I would have caught in a code review — after a bug report from a user. Copilot caught it during design.

    The instruction files were doing their job: Copilot knew that response.data is JSONB keyed by fieldId, that analytics and exports all read from that structure, and that changing a field's type mid-life would silently corrupt historical data. It had the full mental model of the system because I'd written it down.


    Tech Stack

    LayerTechnology
    FrontendReact 18, Vite, TypeScript, Tailwind CSS, shadcn/ui
    AIVercel AI SDK v5, Azure OpenAI
    StateApollo Client (server), Zustand slices (local)
    RealtimeY.js + Hocuspocus WebSocket
    BackendExpress.js, Apollo Server (GraphQL SDL)
    DatabasePostgreSQL + Prisma ORM
    Authbetter-auth (cookie + bearer, org plugin)
    BillingChargebee
    TestingVitest (unit), Playwright + Cucumber (E2E)
    DeploymentAzure Container Apps (backend) + Cloudflare Pages (frontend)

    GitHub Repository

    github.com/dculussoftwares/dculus-forms

    The .github/instructions/ folder, custom agents, and skills are all in the repo — feel free to use the pattern for your own project.



    Demo URL:

    {% embed https://form-app-dev.dculus.com/ %}

    What I Learned

    Write the context down first, then write the code. The 8 instruction files took about 2 hours to write and saved me 10x that in correction loops. Every time Copilot produced something architecturally wrong without them, it was because I hadn't told it how this specific codebase works.

    Specs as prompts. I wrote design specs in docs/superpowers/specs/ before writing a line of code. These double as context for Copilot — feeding it a spec file produces dramatically better first drafts than "hey, build me a thing."

    The schema cache story is a Copilot story. I described the performance problem (schema read on every message). Copilot suggested the TTL cache + invalidation endpoint pattern, explained why a push invalidation is better than a TTL-only approach for a real-time collaborative document, and wrote the first draft. I reviewed it, tweaked the TTL, and shipped it.


    Built with GitHub Copilot. Finished for the GitHub Finish-Up-A-Thon Challenge.

    Tags

    devchallengegithubchallengegithubcopilotwebdev

    Comments

    More Blog

    View all
    Five Gemma-4 models, one accelerator: what porting E2B 31B to AWS Inferentia2 taught megemma

    Five Gemma-4 models, one accelerator: what porting E2B 31B to AWS Inferentia2 taught me

    I ported the whole Gemma-4 family — E2B, E4B, 12B, 31B, and the 26B-A4B MoE — to run on...

    X
    xbill
    Hey DEV, I'm Tobore. Let's actually connect.community

    Hey DEV, I'm Tobore. Let's actually connect.

    Hey DEV, I'm Tobore. Let's actually connect. I've been on here for a while now, mostly writing and...

    L
    Laurina Ayarah
    I burned through thousands of AI tokens. Then a friend did it for freeai

    I burned through thousands of AI tokens. Then a friend did it for free

    (yep, kinda clickbait, just for the funsies 😊) At the beginning of the year, I relaunched my...

    P
    Paulo Henrique
    Claude might be saturating your machineai

    Claude might be saturating your machine

    My laptop was sitting idle with the fan at full tilt. Nothing was running that I knew of. The culprit...

    S
    Sidhant Panda
    Automated GitHub Code Reviews Using Google Geminigithubactions

    Automated GitHub Code Reviews Using Google Gemini

    I Built a Thing! TL;DR — Google Gemini-based Pull Request reviews and Issue Triaging for...

    D
    Darren "Dazbo" Lester
    What is an "agentic harness," actually?ai

    What is an "agentic harness," actually?

    I've been hearing the word "harness" thrown around a lot lately. I assumed it just meant "the IDE" or...

    T
    Tilde A. Thurium

    Stay up to date

    Get the latest Midjourney prompts, rules, and resources delivered to your inbox weekly.

    Neura Market LogoNeura Market

    Discover the best AI prompts, plugins, and resources for Midjourney and more.

    Content Types

    • Rules
    • Prompts
    • MCPs
    • Agents
    • Guides

    Platforms

    • ChatGPT Directory
    • Claude Directory
    • Gemini Directory
    • Cursor Directory
    • Grok Directory
    • Perplexity Directory
    • DeepSeek Directory
    • CoPilot Directory
    • Stable Diffusion Directory
    • Midjourney Directory
    • All Directories

    Resources

    • Blog
    • Documentation
    • Help Center
    • Marketplace

    Legal

    • Privacy Policy
    • Terms of Service

    © 2026 Neura Market. All rights reserved.

    |

    Not affiliated with any AI platform vendors.

    Neura Market

    Custom AI Systems & Services

    Our team of experienced AI builders will help build custom AI systems, workflows, and solutions for your business.

    Request custom work

    Ready-made automations for this

    Workflows from the Neura Market marketplace related to this Midjourney resource

    • Process Contact Form Submissions with Validation and MongoDB Storagen8n · $14.99 · Related topic
    • Automate LinkedIn Profile Discovery and Outreach via Form Submissionn8n · $14.99 · Related topic
    • Automate Graphic Wallpaper Creation with Midjourney and Canvas APIsn8n · $9.99 · Related topic
    • Create a New User in Notion Based on the Signup Form Submissionn8n · $9.99 · Related topic
    Browse all workflows