From 6ed2d4cdab24a5fb3dea8e8808af22199ffeca87 Mon Sep 17 00:00:00 2001 From: Omid Zamani Date: Sat, 8 Aug 2026 10:29:22 +0200 Subject: [PATCH] Add Webxdc packaging via make build-xdc Export a static site when WEBXDC=1 and zip it into dist/squig.xdc so the app can run offline as a Delta Chat mini-app, without changing the normal server build for squig.sh. --- .gitignore | 2 ++ Makefile | 47 ++++++++++++++++++++++++++++++++++++++++ app/opengraph-image.tsx | 2 ++ app/twitter-image.tsx | 2 ++ next.config.ts | 11 ++++++++++ package.json | 1 + public/icon.png | Bin 0 -> 2327 bytes public/manifest.toml | 4 ++++ public/webxdc.js | 21 ++++++++++++++++++ 9 files changed, 90 insertions(+) create mode 100644 Makefile create mode 100644 public/icon.png create mode 100644 public/manifest.toml create mode 100644 public/webxdc.js diff --git a/.gitignore b/.gitignore index 051d5bd..f1718a2 100644 --- a/.gitignore +++ b/.gitignore @@ -19,6 +19,8 @@ # production /build +/dist +/*.xdc # misc .DS_Store diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..ddb1997 --- /dev/null +++ b/Makefile @@ -0,0 +1,47 @@ +.PHONY: help install dev build build-xdc package clean + +.DEFAULT_GOAL := help + +PNPM ?= pnpm +APP_NAME := squig +OUT_DIR := out +DIST_DIR := dist +OUT_XDC := $(DIST_DIR)/$(APP_NAME).xdc + +help: + @echo "squig" + @echo "" + @echo " make install pnpm install" + @echo " make dev Next.js dev server" + @echo " make build Production Next.js build (server / squig.sh)" + @echo " make build-xdc Static export + Webxdc archive → $(OUT_XDC)" + @echo " make package Alias for make build-xdc" + @echo " make clean Remove .next/, out/, dist/" + +install: + $(PNPM) install + +dev: + $(PNPM) dev + +build: + $(PNPM) build + +# Fully offline Delta Chat mini-app: static HTML/JS/CSS + manifest at zip root. +# webxdc.js is a local-dev shim only — the messenger injects its own at runtime. +build-xdc: + @command -v zip >/dev/null || { echo "zip is required to package .xdc"; exit 1; } + WEBXDC=1 $(PNPM) build + @test -f $(OUT_DIR)/index.html || { echo "static export missing $(OUT_DIR)/index.html"; exit 1; } + @test -f $(OUT_DIR)/manifest.toml || { echo "missing $(OUT_DIR)/manifest.toml (add public/manifest.toml)"; exit 1; } + rm -rf $(DIST_DIR) + mkdir -p $(DIST_DIR) + cd $(OUT_DIR) && zip -9 -r ../$(OUT_XDC) . -x webxdc.js -x 'webxdc.js' + @echo "" + @echo " → $(OUT_XDC)" + @echo " Send this file into a Delta Chat chat and tap Start." + +package: build-xdc + +clean: + rm -rf .next out dist diff --git a/app/opengraph-image.tsx b/app/opengraph-image.tsx index 3f7a4c3..b51a276 100644 --- a/app/opengraph-image.tsx +++ b/app/opengraph-image.tsx @@ -3,6 +3,8 @@ import { createSocialImage } from "./social-image"; export const alt = "A blue doodle of a bear sketching a wireframe"; export const size = { width: 1200, height: 630 }; export const contentType = "image/png"; +// Required for `output: "export"` (webxdc packaging). +export const dynamic = "force-static"; export default function OpenGraphImage() { return createSocialImage(); diff --git a/app/twitter-image.tsx b/app/twitter-image.tsx index d31491c..b2acf42 100644 --- a/app/twitter-image.tsx +++ b/app/twitter-image.tsx @@ -3,6 +3,8 @@ import { createSocialImage } from "./social-image"; export const alt = "A blue doodle of a bear sketching a wireframe"; export const size = { width: 1200, height: 630 }; export const contentType = "image/png"; +// Required for `output: "export"` (webxdc packaging). +export const dynamic = "force-static"; export default function TwitterImage() { return createSocialImage(); diff --git a/next.config.ts b/next.config.ts index b39ddf4..3a7d93e 100644 --- a/next.config.ts +++ b/next.config.ts @@ -2,6 +2,10 @@ import type { NextConfig } from "next"; import path from "node:path"; import { fileURLToPath } from "node:url"; +// Webxdc packaging (`make build-xdc`) needs a fully static site at out/. +// Normal `pnpm build` keeps the default Next server output for squig.sh. +const webxdc = process.env.WEBXDC === "1"; + const nextConfig: NextConfig = { // Pin the workspace root to this repo. // @@ -14,6 +18,13 @@ const nextConfig: NextConfig = { turbopack: { root: path.dirname(fileURLToPath(import.meta.url)), }, + ...(webxdc + ? { + output: "export" as const, + // next/image optimizers need a server; webxdc ships plain files. + images: { unoptimized: true }, + } + : {}), }; export default nextConfig; diff --git a/package.json b/package.json index f880d90..4ec5920 100644 --- a/package.json +++ b/package.json @@ -6,6 +6,7 @@ "scripts": { "dev": "next dev", "build": "next build", + "build:xdc": "make build-xdc", "start": "next start", "lint": "eslint", "test": "tsc --noEmit -p scripts/tsconfig.json && node --experimental-strip-types --import ./scripts/register-loader.mjs scripts/test-geometry.ts && node --experimental-strip-types --import ./scripts/register-loader.mjs scripts/test-selection.ts && node --experimental-strip-types --import ./scripts/register-loader.mjs scripts/test-clipboard.ts && node --experimental-strip-types --import ./scripts/register-loader.mjs scripts/test-text.ts" diff --git a/public/icon.png b/public/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..592b11f3e3049f756f5cab536c2b1115f9cb5173 GIT binary patch literal 2327 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 kwDY_F=fHk{ZX<_8?`B)OV literal 0 HcmV?d00001 diff --git a/public/manifest.toml b/public/manifest.toml new file mode 100644 index 0000000..93bcceb --- /dev/null +++ b/public/manifest.toml @@ -0,0 +1,4 @@ +name = "squig" +description = "Wireframes that know they're wireframes — an infinite sketch canvas of real UI components." +source_code_url = "https://github.com/pablostanley/squig" +icon = "icon.png" diff --git a/public/webxdc.js b/public/webxdc.js new file mode 100644 index 0000000..ea1d591 --- /dev/null +++ b/public/webxdc.js @@ -0,0 +1,21 @@ +// Local-dev shim only. Delta Chat injects window.webxdc at runtime — +// never ship this file inside the .xdc (make build-xdc excludes it). +if (typeof window !== "undefined" && !window.webxdc) { + window.webxdc = { + sendUpdate() {}, + setUpdateListener(cb) { + return Promise.resolve().then(() => cb && undefined); + }, + getAllUpdates() { + return Promise.resolve([]); + }, + sendToChat() { + return Promise.resolve(); + }, + importFiles() { + return Promise.resolve([]); + }, + selfAddr: "dev@local", + selfName: "Dev", + }; +}