MolinoPro

SKILL_projection-appscript

Master Codebase Guidebook
Markdown + HTML Dev-Docs Renderer - Frontend Client Module

Default Index
Open README.md
Root: README.mdarchive
Milestones
H1SKILL: Projection Layer — Apps Script & Google Workspace

Module: External Output Layer Status: Active — canonical contract defined Part of: MASTER_SKILL.md — Chapter 8 See also: ../appscript.apis.md, ../appscript-office-server-firstLoop.md


H2Identity

Apps Script = stateless external office execution layer.

It does NOT:

  • Own business logic
  • Access Prisma
  • Make decisions about entities
  • Know about internal app state

It ONLY:

  • Executes Google Workspace operations
  • Receives fully computed payloads
  • Returns structured results

Principle: Compute in Next.js → project externally via Apps Script.


H2Authority Map
LayerDoes
Apps ScriptThin atomic actions against Google Workspace
Next.js server actionsBusiness orchestration, compute, permissions, Prisma
UI / toolbarIntent launchers only — no secrets, no Google logic

H2Canonical Request/Response Contract
H3Request (Next.js → Apps Script)
type GoogleToolRequest<TInput = unknown> = {
  action: string;
  requestId: string;
  source: "folio" | "trip" | "product" | "experience" | "offer" | "assistant";
  entityType?: string;
  entityId?: string | number;
  documentId?: number;
  userId?: string;
  payload: TInput;
};
H3Response (Apps Script → Next.js)
type GoogleToolResponse<TResult = unknown> = {
  ok: boolean;
  action: string;
  requestId: string;
  timestamp: string; // ISO string
  result?: TResult;
  error?: {
    code: string;
    message: string;
    details?: unknown;
  };
  meta?: {
    spreadsheetId?: string;
    documentId?: string;
    fileId?: string;
    fileUrl?: string;
    rowsProcessed?: number;
    batchComplete?: boolean;
    nextCursor?: string | number | null;
    durationMs?: number;
  };
};

H2Apps Script Router (doPost)
function doPost(e) {
  const startedAt = Date.now();
  try {
    const body = JSON.parse(e.postData.contents || "{}");
    const action = body.action;
    const requestId = body.requestId || Utilities.getUuid();

    let result;
    switch (action) {
      case "coreOffice.generateTemplates":
        result = CoreOfficeHandlers.generateTemplates(body.payload || {});
        break;
      case "docs.create":
        result = DocsHandlers.create(body.payload || {});
        break;
      case "docs.exportPdf":
        result = DocsHandlers.exportPdf(body.payload || {});
        break;
      case "gmail.send":
        result = GmailHandlers.send(body.payload || {});
        break;
      case "calendar.createEvent":
        result = CalendarHandlers.createEvent(body.payload || {});
        break;
      case "sheets.appendRows":
        result = SheetsHandlers.appendRows(body.payload || {});
        break;
      // ... etc.
      default:
        return jsonResponse({ ok: false, error: { code: "UNKNOWN_ACTION" }, ... });
    }

    return jsonResponse({
      ok: true, action, requestId,
      timestamp: new Date().toISOString(),
      result,
      meta: { durationMs: Date.now() - startedAt }
    });
  } catch (err) {
    return jsonResponse({ ok: false, error: { code: "INTERNAL", message: err.message }, ... });
  }
}

H2Tier 1: Generic Tool Endpoints
H3From Next.js → GAS via API routes:
POST /api/tools/google/docs/create
POST /api/tools/google/docs/export-pdf
POST /api/tools/google/docs/append-content
POST /api/tools/google/gmail/send
POST /api/tools/google/gmail/create-draft
POST /api/tools/google/sheets/append
POST /api/tools/google/sheets/get-rows
POST /api/tools/google/calendar/create-event
POST /api/tools/google/calendar/update-event
POST /api/tools/google/drive/create-file
POST /api/tools/google/drive/share-file
POST /api/tools/google/drive/export-pdf

H2Tier 2: App-Level Orchestrators (Next Server Actions)

These call Tier 1 endpoints with entity-aware business logic:

createTripAgendaAndInviteGuests({ tripId, guestEmails });
publishOfferAsPdfAndEmail({ offerId, clientEmail });
logMeetingAndCreateFollowup({ meetingData, followupDate });
buildClientBriefAndShare({ tripId, clientInfo });
createTripDaySchedule({ tripId, days });
sendPostSessionSummary({ sessionId, attendeeEmails });

H2Output Bundle (for trip exports)
type OutputBundle = {
  meta: {
    entity: string; // "trip" | "offer" | "order"
    entityId: string;
    title: string;
  };
  document?: {
    // for docs.create
    title: string;
    sections: { heading?: string; content: string }[];
  };
  calendar?: Array<{
    // for calendar.createEvent
    title: string;
    startDateTime: string;
    endDateTime: string;
    description?: string;
    location?: string;
    guests?: string[];
  }>;
  tabular?: {
    // for sheets.appendRows
    sheetId?: string;
    rows: Record<string, any>[];
  };
};

H2Trip Projection Paths
Trip (canonical entity)
  ↓ computeBundle(trip)
OutputBundle
  ↓ projectEntity(bundle)
  ├── → docs.create      → Generated Google Doc
  ├── → docs.exportPdf   → PDF
  ├── → gmail.send       → Email to client
  └── → calendar.create  → Calendar event + invites

H2Security Note
  • Apps Script Web App must be deployed with appropriate access level
  • requestId enables correlation and audit logging
  • Never expose the GAS web app URL in client-side code
  • All calls to GAS go through Next.js server actions only

This skill is Chapter 8 of MASTER_SKILL.md. See also: ../appscript.apis.md for full action catalog.