Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 16 additions & 16 deletions .env.example
Original file line number Diff line number Diff line change
@@ -1,5 +1,14 @@
# Local development
VITE_APP_PROFILE=desktop
VITE_DESKTOP_RELEASES_URL=
VITE_DOWNLOAD_WINDOWS_URL=
VITE_DOWNLOAD_MACOS_URL=
VITE_DOWNLOAD_LINUX_URL=

# Current desktop/web profiles keep LLM, remote share, Google Workspace, and
# remote TeX calls disabled by default. The server values below are retained for
# legacy service development only; do not set them in Vercel unless those remote
# services are intentionally re-enabled.
GOOGLE_GENAI_USE_VERTEXAI=true
GOOGLE_CLOUD_PROJECT=urban-dds
GOOGLE_CLOUD_LOCATION=asia-northeast3
Expand Down Expand Up @@ -57,21 +66,15 @@ GOOGLE_WORKSPACE_SCOPE_PROFILE=restricted
GOOGLE_WORKSPACE_SCOPES=
WORKSPACE_STATE_PATH=
WORKSPACE_DB_PATH=
# Local development defaults to a file-backed workspace repository.
# Cloud Run defaults to Firestore so OAuth/session state is shared across instances.
# Override only if you need to force a backend explicitly.
# Docsy now uses the local file-backed workspace repository in every runtime.
# Set WORKSPACE_STATE_PATH or WORKSPACE_DB_PATH to a writable persistent local
# path when the runtime should keep OAuth/session state across restarts.
# WORKSPACE_REPOSITORY_BACKEND=file
# Optional Firestore namespace overrides for deployed workspace state.
# WORKSPACE_FIRESTORE_ROOT_COLLECTION=docsyWorkspace
# WORKSPACE_FIRESTORE_ROOT_DOCUMENT=state

# TeX service hardening
TEX_MAX_REQUEST_BYTES=400000
TEX_PREVIEW_BUCKET=
TEX_PREVIEW_PUBLIC_BASE_URL=
TEX_PREVIEW_URL_TTL_SECONDS=900
TEX_TASK_QUEUE=
TEX_TASK_LOCATION=
TEX_JOB_WORKER_URL=
# Keep full LaTeX document wrappers enabled for deployed LaTeX mode.
# Public/demo deployments should accept arbitrary installed packages and only
# block dangerous file/process primitives by default.
Expand All @@ -80,9 +83,6 @@ TEX_ALLOW_ALL_PACKAGES=true
TEX_ALLOW_RAW_DOCUMENT=true
TEX_ALLOW_RESTRICTED_COMMANDS=false
TEX_ALLOWED_PACKAGES=amsmath,amssymb,amsthm,array,booktabs,caption,enumitem,etoolbox,fancyhdr,float,fontspec,geometry,graphicx,hyperref,inputenc,latexsym,listings,longtable,makecell,mathtools,multirow,setspace,soul,tabularx,tcolorbox,titlesec,ulem,xcolor,xeCJK
# For Cloud Run preview delivery, set TEX_PREVIEW_BUCKET to a GCS bucket that the
# TeX service account can write to and sign read URLs for. Signed preview URLs
# expire after TEX_PREVIEW_URL_TTL_SECONDS (default 15 minutes).
# For async preview/export, set TEX_TASK_QUEUE, TEX_TASK_LOCATION, and
# TEX_JOB_WORKER_URL on the interactive TeX service so it can enqueue Cloud Tasks
# for the dedicated worker service.
# Signed local preview/download URLs expire after TEX_PREVIEW_URL_TTL_SECONDS
# (default 15 minutes). Set TEX_PREVIEW_PUBLIC_BASE_URL only when the service
# must advertise a public local artifact origin instead of loopback.
1 change: 1 addition & 0 deletions .github/workflows/deploy-full-stack.yml
Original file line number Diff line number Diff line change
Expand Up @@ -701,6 +701,7 @@ jobs:
printf "VITE_APP_PROFILE=web\nVITE_AI_API_BASE_URL=%s\nVITE_APP_BUILD_ID=%s\n" "${AI_API_BASE_URL}" "${FRONTEND_BUILD_ID}" > .env.web.local

npm ci
npm run typecheck:app
npm run build:web

- name: Verify frontend file
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/deploy-web.yml
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,7 @@ jobs:
printf "VITE_APP_PROFILE=web\nVITE_AI_API_BASE_URL=%s\nVITE_APP_BUILD_ID=%s\n" "${AI_API_BASE_URL}" "${FRONTEND_BUILD_ID}" > .env.web.local

npm ci
npm run typecheck:app
npm run build:web

- name: Verify frontend file
Expand Down
124 changes: 124 additions & 0 deletions .github/workflows/release-desktop.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
name: Release Desktop Apps

on:
push:
tags:
- "desktop-v*"
workflow_dispatch:
inputs:
tag:
description: "Release tag to create or update"
required: true
type: string

permissions:
contents: write

jobs:
build:
name: Build ${{ matrix.label }}
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
include:
- label: Windows
os: windows-latest
- label: macOS
os: macos-15-intel
- label: Linux
os: ubuntu-latest

steps:
- name: Checkout
uses: actions/checkout@v6

- name: Setup Node
uses: actions/setup-node@v6
with:
cache: npm
node-version: 22

- name: Install Linux packaging dependencies
if: runner.os == 'Linux'
run: |
sudo apt-get update
sudo apt-get install -y libarchive-tools rpm xvfb

- name: Install dependencies
run: npm ci

- name: Typecheck
run: npm run typecheck

- name: Build desktop renderer and Electron entry
run: |
npm run build
npm run electron:build

- name: Package desktop app
env:
CSC_IDENTITY_AUTO_DISCOVERY: "false"
run: npx electron-builder --publish never

- name: Verify packaged desktop renderer
run: npm run verify:desktop-package

- name: Smoke packaged desktop first screen
if: runner.os != 'Linux'
run: npm run verify:desktop-first-screen

- name: Smoke packaged desktop first screen on Linux
if: runner.os == 'Linux'
run: xvfb-run -a npm run verify:desktop-first-screen

- name: Collect release assets
shell: bash
run: |
set -euo pipefail
mkdir -p desktop-artifacts
find release -maxdepth 1 -type f \( \
-name "*.AppImage" -o \
-name "*.deb" -o \
-name "*.dmg" -o \
-name "*.exe" -o \
-name "*.zip" \
\) -exec cp {} desktop-artifacts/ \;
ls -la desktop-artifacts
test "$(find desktop-artifacts -type f | wc -l)" -gt 0

- name: Upload release assets
uses: actions/upload-artifact@v7
with:
name: desktop-${{ runner.os }}
path: desktop-artifacts/*
if-no-files-found: error

publish:
name: Publish GitHub Release
needs: build
runs-on: ubuntu-latest
steps:
- name: Download release assets
uses: actions/download-artifact@v8
with:
merge-multiple: true
path: release-assets

- name: Publish release
env:
GH_TOKEN: ${{ github.token }}
RELEASE_REPO: ${{ github.repository }}
RELEASE_TAG: ${{ github.event.inputs.tag || github.ref_name }}
run: |
set -euo pipefail
ls -la release-assets
if gh release view "${RELEASE_TAG}" --repo "${RELEASE_REPO}" >/dev/null 2>&1; then
gh release upload "${RELEASE_TAG}" release-assets/* --repo "${RELEASE_REPO}" --clobber
else
gh release create "${RELEASE_TAG}" release-assets/* \
--repo "${RELEASE_REPO}" \
--latest \
--title "Docsy Desktop ${RELEASE_TAG}" \
--notes "Local-first Docsy desktop builds for Windows, macOS, and Linux."
fi
7 changes: 7 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ lerna-debug.log*
node_modules
dist
server-dist
electron-dist
/release
dist-ssr
.vite
coverage
Expand Down Expand Up @@ -43,6 +45,7 @@ Thumbs.db

# Local/temporary files
.data/
.playwright-cli/
*.local
*.tmp
*.temp
Expand All @@ -54,6 +57,10 @@ temp-*.ts
temp-*.tsx
test-results
output/gcp-monitoring/
output/playwright/
output/readme-bundles/
output/web-profile-test/
playwright-report
.playwright-*.png

.vercel
16 changes: 16 additions & 0 deletions .vercelignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
/release
/dist
/electron-dist
/server-dist
/node_modules
/.omx
/.playwright-cli
/test-results
/output
*.har
*.log
*.err
*.out
.env
.env.*
!.env.example
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ Current submission priorities:

## Architecture Diagram

Docsy's deployed architecture uses a React + Vite frontend served from Firebase Hosting and a separate Node.js AI backend deployed on Cloud Run. The backend accesses Gemini on Vertex AI through the Google GenAI SDK, stores shared Google Workspace session and state data in Firestore, and brokers Google Docs/Drive plus LaTeX service requests without exposing credentials to the browser.
Docsy's deployed architecture uses a React + Vite frontend served from Firebase Hosting and a separate Node.js AI backend deployed on Cloud Run. The backend accesses Gemini on Vertex AI through the Google GenAI SDK, stores Google Workspace session and state data in the local file-backed repository, and brokers Google Docs/Drive plus LaTeX service requests without exposing credentials to the browser.

For hackathon submission, export the diagram below as a PNG and upload it via `File Upload`.

Expand All @@ -70,8 +70,8 @@ flowchart LR
GEM -->|"structured JSON action<br/>patch proposal"| API
API -->|"assistant response<br/>patch data"| FE

API -->|"session + workspace state"| DB["Firestore"]
DB -->|"shared state lookup"| API
API -->|"session + workspace state"| DB["Local state file"]
DB -->|"local state lookup"| API

API -->|"OAuth + Docs/Drive operations"| GW["Google OAuth + Google Docs/Drive"]
GW -->|"auth callback + document data"| API
Expand Down Expand Up @@ -201,7 +201,7 @@ Google OAuth production guard:

- set `GOOGLE_OAUTH_PUBLISHING_STATUS=testing|production`
- set `GOOGLE_WORKSPACE_SCOPE_PROFILE=restricted|reduced`
- deployed Google Workspace state now defaults to Firestore on Cloud Run so OAuth/session state is shared across instances
- deployed Google Workspace state now uses the local file-backed repository; set `WORKSPACE_STATE_PATH` or `WORKSPACE_DB_PATH` to a writable persistent local path when state must survive restarts
- Firebase Hosting rewrites only forward the `__session` cookie to Cloud Run, so hosted workspace auth must use that cookie name in deployed HTTPS environments
- the current deployed auth contract is documented in [docs/current-workspace-auth-contract-2026-03-16.md](docs/current-workspace-auth-contract-2026-03-16.md)
- run `npm run check:public-deploy` before public deploys to validate custom-domain and OAuth settings
Expand Down
2 changes: 1 addition & 1 deletion cloudbuild.ai.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ substitutions:
_GOOGLE_WORKSPACE_SCOPE_PROFILE: "restricted"
_WORKSPACE_FRONTEND_ORIGIN: ""
_PUBLIC_DEPLOY_EXPECTED_FRONTEND_ORIGIN: ""
_WORKSPACE_REPOSITORY_BACKEND: "firestore"
_WORKSPACE_REPOSITORY_BACKEND: "file"
_GOOGLE_WORKSPACE_SCOPES: ""
_TEX_ALLOW_ALL_PACKAGES: "true"
_TEX_ALLOW_RAW_DOCUMENT: "true"
Expand Down
59 changes: 3 additions & 56 deletions cloudbuild.tex.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -19,49 +19,7 @@ steps:
args:
- -ceu
- |
if [ -z "${_TEX_PREVIEW_BUCKET}" ] || [ "${_TEX_PREVIEW_BUCKET}" = "__SET_ME__" ]; then
echo "_TEX_PREVIEW_BUCKET must be configured for Cloud Run deployments." >&2
exit 1
fi

if [ -z "${_TEX_TASK_QUEUE}" ] || [ -z "${_TEX_TASK_LOCATION}" ]; then
echo "Cloud Tasks queue is not configured. TeX service will use local background processing."
elif ! gcloud tasks queues describe "${_TEX_TASK_QUEUE}" --location="${_TEX_TASK_LOCATION}" >/dev/null 2>&1; then
gcloud tasks queues create "${_TEX_TASK_QUEUE}" --location="${_TEX_TASK_LOCATION}"
fi

cat > /workspace/tex-worker-env.yaml <<EOF
TEX_COMPILE_TIMEOUT_MS: "${_TEX_COMPILE_TIMEOUT_MS}"
TEX_MAX_SOURCE_BYTES: "${_TEX_MAX_SOURCE_BYTES}"
TEX_MAX_REQUEST_BYTES: "${_TEX_MAX_REQUEST_BYTES}"
TEX_MAX_CONCURRENCY: "${_TEX_MAX_CONCURRENCY}"
TEX_ALLOW_ALL_PACKAGES: "${_TEX_ALLOW_ALL_PACKAGES}"
TEX_ALLOW_RAW_DOCUMENT: "${_TEX_ALLOW_RAW_DOCUMENT}"
TEX_ALLOW_RESTRICTED_COMMANDS: "${_TEX_ALLOW_RESTRICTED_COMMANDS}"
TEX_ALLOWED_PACKAGES: "${_TEX_ALLOWED_PACKAGES}"
TEX_PREVIEW_BUCKET: "${_TEX_PREVIEW_BUCKET}"
TEX_PREVIEW_URL_TTL_SECONDS: "${_TEX_PREVIEW_URL_TTL_SECONDS}"
NODE_OPTIONS: "${_WORKER_NODE_OPTIONS}"
EOF

gcloud run deploy "${_WORKER_SERVICE_NAME}" \
--image="${_IMAGE_URI}" \
--region="${_REGION}" \
--platform=managed \
--allow-unauthenticated \
--port=8082 \
--command=node \
--args=server-dist/texWorker.js \
--memory="${_WORKER_RUN_MEMORY}" \
--cpu="${_WORKER_RUN_CPU}" \
--concurrency="${_WORKER_RUN_CONCURRENCY}" \
--max-instances="${_WORKER_RUN_MAX_INSTANCES}" \
--ingress=all \
--service-account="${_RUN_SERVICE_ACCOUNT}" \
--env-vars-file=/workspace/tex-worker-env.yaml \
--update-secrets="TEX_SERVICE_AUTH_TOKEN=${_TEX_SERVICE_AUTH_TOKEN_SECRET}:latest"

WORKER_URL="$(gcloud run services describe "${_WORKER_SERVICE_NAME}" --region="${_REGION}" --format='value(status.url)')"
echo "TeX service uses local job processing and local artifact URLs."

cat > /workspace/tex-service-env.yaml <<EOF
TEX_COMPILE_TIMEOUT_MS: "${_TEX_COMPILE_TIMEOUT_MS}"
Expand All @@ -72,11 +30,8 @@ steps:
TEX_ALLOW_RAW_DOCUMENT: "${_TEX_ALLOW_RAW_DOCUMENT}"
TEX_ALLOW_RESTRICTED_COMMANDS: "${_TEX_ALLOW_RESTRICTED_COMMANDS}"
TEX_ALLOWED_PACKAGES: "${_TEX_ALLOWED_PACKAGES}"
TEX_PREVIEW_BUCKET: "${_TEX_PREVIEW_BUCKET}"
TEX_PREVIEW_PUBLIC_BASE_URL: "${_TEX_PREVIEW_PUBLIC_BASE_URL}"
TEX_PREVIEW_URL_TTL_SECONDS: "${_TEX_PREVIEW_URL_TTL_SECONDS}"
TEX_TASK_QUEUE: "${_TEX_TASK_QUEUE}"
TEX_TASK_LOCATION: "${_TEX_TASK_LOCATION}"
TEX_JOB_WORKER_URL: "$${WORKER_URL}"
NODE_OPTIONS: "${_NODE_OPTIONS}"
EOF

Expand All @@ -97,15 +52,10 @@ steps:
substitutions:
_REGION: asia-northeast3
_SERVICE_NAME: docsy-tex
_WORKER_SERVICE_NAME: docsy-tex-worker
_RUN_MEMORY: 4Gi
_RUN_CPU: "2"
_RUN_CONCURRENCY: "1"
_RUN_MAX_INSTANCES: "5"
_WORKER_RUN_MEMORY: 4Gi
_WORKER_RUN_CPU: "2"
_WORKER_RUN_CONCURRENCY: "1"
_WORKER_RUN_MAX_INSTANCES: "5"
_TEX_COMPILE_TIMEOUT_MS: "15000"
_TEX_MAX_SOURCE_BYTES: "300000"
_TEX_MAX_REQUEST_BYTES: "300000"
Expand All @@ -114,13 +64,10 @@ substitutions:
_TEX_ALLOW_RAW_DOCUMENT: "true"
_TEX_ALLOW_RESTRICTED_COMMANDS: "false"
_TEX_ALLOWED_PACKAGES: "amsmath,amssymb,amsthm,array,booktabs,caption,enumitem,etoolbox,fancyhdr,float,fontspec,geometry,graphicx,hyperref,inputenc,latexsym,listings,longtable,makecell,mathtools,multirow,setspace,soul,tabularx,tcolorbox,titlesec,ulem,xcolor,xeCJK"
_TEX_PREVIEW_BUCKET: "__SET_ME__"
_TEX_PREVIEW_PUBLIC_BASE_URL: ""
_TEX_PREVIEW_URL_TTL_SECONDS: "900"
_TEX_TASK_QUEUE: tex-compile
_TEX_TASK_LOCATION: asia-northeast3
_TEX_SERVICE_AUTH_TOKEN_SECRET: tex-service-auth-token
_NODE_OPTIONS: "--max-old-space-size=512"
_WORKER_NODE_OPTIONS: "--max-old-space-size=512"
_RUN_SERVICE_ACCOUNT: "399359781191-compute@developer.gserviceaccount.com"
images:
- ${_IMAGE_URI}
Loading
Loading