Extra A - Package as an app
The main tutorial finished a Todo app as normal HTML. This extra keeps that Sercrod logic unchanged, prepares the web files for Capacitor, and shows the path to an Android debug APK.
This is not a Capacitor tutorial. The goal is to make the Sercrod side clean: one app entry, local assets, and no hidden dependency on the public documentation site.
This extra shows how to create an Android APK that contains the finished Sercrod Todo app. The same prepared Sercrod files can also be used as the app contents for an iOS IPA. However, IPA creation depends more on each developer's environment, so we do not cover it here.
Target shape
Capacitor expects a directory of web files. Put the finished Todo page and the files it needs into one app-focused directory.
todo-app/
index.html
sercrod.js
adapters/
capacitor/
filesystem.js
assets/
style.css
icons...
The wrapper reads this directory as its web app. Sercrod still owns the
Todo behavior inside index.html.
Prepare the page
- Start from the completed Todo example from 09 - Save and load.
- Use one app entry file, usually
index.html. - Copy
dist/sercrod.jsinto the app assercrod.js. - If the app uses file directives, copy
dist/adapters/capacitor/filesystem.jsinto the app too. - Copy only the CSS, icons, and assets the app actually uses.
- Keep the Todo code the same:
*input,*for,@click,*save.file, and*load.filestill work in the web page.
Example app index
For the app package, create an index.html inside the prepared
app directory. Notice that sercrod.js is loaded with a
relative path.
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Sercrod Todo</title>
</head>
<body>
<script src="./sercrod.js"></script>
<script src="./adapters/capacitor/filesystem.js"></script>
<serc-rod data='{"draft":"","todos":[{"title":"Buy milk","done":false},{"title":"Pay bills","done":true}]}'>
<h2>Todo</h2>
<p *if="todos.length === 0">No todos yet.</p>
<ul *else>
<li *for="todo of todos">
<span *if="todo.done">[x]</span>
<span *else>[ ]</span>
<span>%todo.title%</span>
<button type="button" @click="todo.done = !todo.done">
Toggle
</button>
</li>
</ul>
<p><label>Title: <input type="text" *input="draft" *lazy></label></p>
<p *if="draft">Draft: %draft%</p>
<p *else>Type a Todo title.</p>
<button type="button" @click="if(draft.trim()){ todos.push({ title: draft, done: false }); draft = '' }">
Add
</button>
<button type="button" *save.file>Save</button>
<button type="button" *load.file>Load</button>
</serc-rod>
</body>
</html>
Use app paths
A documentation page can depend on site paths such as
/assets/style.css or /sercrod.js. An app
package should be more self-contained.
<link rel="stylesheet" href="./assets/style.css">
<script src="./sercrod.js"></script>
<script src="./adapters/capacitor/filesystem.js"></script>
- Prefer local relative paths for files that ship with the app.
- Avoid links that only make sense on the public documentation site.
- Keep external network calls out of this minimal app unless the app really needs them.
File directives in Capacitor
In a normal browser, *save.file and *load.file can use
the browser's download and file selection features.
Inside a Capacitor Android app, however, a WebView does not handle device storage in exactly the same way as a regular browser. In particular, writing data to the device is usually better routed through Capacitor's own APIs.
For that reason, when a Capacitor app uses file-related directives such
as *save.file or *load.file, load
dist/adapters/capacitor/filesystem.js in addition to
dist/sercrod.js.
The HTML does not change. The adapter connects the same Sercrod directives to Capacitor's file handling.
For browser-local JSON persistence that does not need device file
pickers, use *save.store="'key'" and
*load.store="'key'". Those store forms use IndexedDB in
the WebView and do not use the Capacitor filesystem adapter.
When debugging an app build, check the adapter from the WebView console:
window.__Sercrod.adapter_map.file
window.SercrodCapacitorFilesystemAdapter.diagnose()
localStorage.getItem("sercrod-capacitor-filesystem:last-file")
localStorage.getItem("sercrod-capacitor-filesystem:last-status")
The file role should be capacitor.filesystem, and
has_filesystem, has_write_file, and
has_read_file should be true. The last-file record shows
the directory and file name Sercrod will try to load next. The last-status
record shows whether save or load succeeded, which directories were tried,
and any plugin error messages. If the adapter script loads but the
Filesystem plugin is missing, install and sync @capacitor/filesystem
in the Capacitor project.
On a phone, the adapter also writes a small status line after the Save
or Load button. After tapping Save, you should see a message such as
Saved: DATA/Sercrod-20260515-213000.json, or a concrete
Save failed message.
Capacitor config
In a Capacitor project, point webDir at the directory that
contains the prepared Sercrod app.
import type { CapacitorConfig } from '@capacitor/cli';
const config: CapacitorConfig = {
appId: 'com.example.sercrodtodo',
appName: 'Sercrod Todo',
webDir: 'todo-app'
};
export default config;
The important Sercrod-side value is webDir. It should point
to the directory that contains index.html,
sercrod.js, and the local assets.
Android handoff
After the Sercrod app directory works as a normal web app, initialize Capacitor, add Android, and sync the web files into the native project.
npm i @capacitor/core
npm i -D @capacitor/cli
npx cap init
npm i @capacitor/android
npm i @capacitor/filesystem
npx cap add android
npx cap sync android
npx cap open android
From there, Android Studio can build the app. A development APK is usually created from the Android project with a debug build.
cd android
./gradlew assembleDebug
On Windows, use the Gradle wrapper batch file instead.
cd android
gradlew.bat assembleDebug
The debug APK is commonly written here:
android/app/build/outputs/apk/debug/app-debug.apk
This APK is enough to prove the handoff: the finished Sercrod Todo app has been packaged as an Android app. Release builds, app store release, and other native plugins are separate Android and Capacitor work.
iOS handoff on macOS
The same prepared web files can be used for an iOS wrapper on macOS. The web app stays the same; only the native shell changes.
- Install Xcode on the Mac that will build the app.
- Add the iOS platform to the Capacitor project.
- Sync the prepared web files into the native iOS project.
- Open the native project in Xcode and build a debug run there.
npm i @capacitor/ios
npx cap add ios
npx cap sync ios
npx cap open ios
Capacitor's iOS target uses Xcode for native builds. The important Sercrod
side detail is still the same webDir and the same prepared
HTML, script, and adapter files.
Capacitor reference
Use the official Capacitor guide for setup details and current platform requirements.
Next
This extra prepares the handoff and reaches an Android debug APK. The Todo app remains a Sercrod web app; the wrapper packages it.