# Deepin UOS Store Debian Packaging Implementation Plan

> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.

**Goal:** Repackage LabelNova as a deepin/UOS store-style `.deb` whose runnable payload lives under `/opt/apps/cn.labelnova.app`.

**Architecture:** Keep the existing Qt shell build and OBS source-package flow, but stop using the `/usr/share/labelnova` runtime layout. Stage the application explicitly in `debian/rules` under `/opt/apps/cn.labelnova.app/files`, ship a store-style desktop launcher and `info` metadata, and keep CMake focused on building the binary plus generic frontend installation for non-Debian local builds.

**Tech Stack:** Debian packaging (`debhelper`, `dpkg-buildpackage`, `osc`), CMake/Qt WebEngine, Node.js assertion tests, deepin/UOS `/opt/apps` store layout

---

## File Map

- Create: `debian/cn.labelnova.app.desktop`
  Purpose: deepin/UOS store launcher under `entries/applications`
- Create: `debian/cn.labelnova.app.info.in`
  Purpose: app metadata template rendered to `/opt/apps/cn.labelnova.app/info`
- Create: `tests/debian-store-layout.test.js`
  Purpose: repo-level regression test for the `/opt/apps` layout, launcher metadata, and packaging rules
- Modify: `debian/control`
  Purpose: add packaging validation dependency and describe the `/opt/apps` delivery model
- Modify: `debian/rules`
  Purpose: build the Qt shell, then stage the runtime tree under `/opt/apps/cn.labelnova.app`
- Delete: `debian/install`
  Purpose: remove the old `/usr`-centric runtime staging list
- Modify: `qt-shell/CMakeLists.txt`
  Purpose: stop generating/installing store metadata from CMake; keep only binary and generic frontend install rules
- Modify: `debian/changelog`
  Purpose: record the `/opt/apps` packaging switch before building and uploading a new source package

### Task 1: Add Store Metadata Fixtures And A Regression Test

**Files:**
- Create: `tests/debian-store-layout.test.js`
- Create: `debian/cn.labelnova.app.desktop`
- Create: `debian/cn.labelnova.app.info.in`

- [ ] **Step 1: Write the failing test**

Create `tests/debian-store-layout.test.js` with this exact content:

```js
const assert = require("assert");
const fs = require("fs");
const path = require("path");

const repoRoot = path.join(__dirname, "..");

function read(relPath) {
  return fs.readFileSync(path.join(repoRoot, relPath), "utf8");
}

const desktop = read("debian/cn.labelnova.app.desktop");
assert(desktop.includes("Exec=/opt/apps/cn.labelnova.app/files/bin/labelnova %U"));
assert(desktop.includes("Icon=cn.labelnova.app"));
assert(desktop.includes("Categories=Graphics;Office;Utility;"));
assert(desktop.includes("Keywords[zh_CN]=标签;条码;二维码;打印;打印机;"));

const info = read("debian/cn.labelnova.app.info.in");
assert(info.includes('"appid": "cn.labelnova.app"'));
assert(info.includes('"name": "LabelNova"'));
assert(info.includes('"version": "@VERSION@"'));
assert(info.includes('"arch": ["@ARCH@"]'));

console.log("debian store layout tests passed");
```

- [ ] **Step 2: Run the test to verify it fails**

Run:

```bash
node tests/debian-store-layout.test.js
```

Expected: FAIL with `ENOENT` for `debian/cn.labelnova.app.desktop` because the store metadata files do not exist yet.

- [ ] **Step 3: Write the minimal implementation**

Create `debian/cn.labelnova.app.desktop` with this content:

```ini
[Desktop Entry]
Version=1.0
Type=Application
Name=LabelNova
Name[zh_CN]=LabelNova
Comment=Desktop label designer with native system printing
Comment[zh_CN]=支持系统打印的桌面标签设计工具
Exec=/opt/apps/cn.labelnova.app/files/bin/labelnova %U
Icon=cn.labelnova.app
Terminal=false
Categories=Graphics;Office;Utility;
Keywords=label;barcode;qrcode;print;printer;
Keywords[zh_CN]=标签;条码;二维码;打印;打印机;
StartupNotify=true
```

Create `debian/cn.labelnova.app.info.in` with this content:

```json
{
  "appid": "cn.labelnova.app",
  "name": "LabelNova",
  "version": "@VERSION@",
  "arch": ["@ARCH@"]
}
```

- [ ] **Step 4: Run the test to verify it passes**

Run:

```bash
node tests/debian-store-layout.test.js
```

Expected: PASS and print `debian store layout tests passed`.

- [ ] **Step 5: Commit**

Run:

```bash
git add tests/debian-store-layout.test.js debian/cn.labelnova.app.desktop debian/cn.labelnova.app.info.in
git commit -m "test: add deepin uos store metadata fixtures"
```

### Task 2: Stage The Runtime Under `/opt/apps/cn.labelnova.app`

**Files:**
- Modify: `tests/debian-store-layout.test.js`
- Modify: `debian/control`
- Modify: `debian/rules`
- Delete: `debian/install`
- Modify: `qt-shell/CMakeLists.txt`

- [ ] **Step 1: Extend the test with packaging-layout assertions**

Append these checks to `tests/debian-store-layout.test.js` after the metadata assertions from Task 1:

```js
const rules = read("debian/rules");
assert(rules.includes("APPID := cn.labelnova.app"));
assert(rules.includes("APPROOT := $(CURDIR)/debian/labelnova/opt/apps/$(APPID)"));
assert(rules.includes("install -m 0755 $(BUILDDIR)/labelnova $(APPFILES)/bin/labelnova"));
assert(rules.includes("cp -a index.html $(APPFILES)/frontend/"));
assert(rules.includes("desktop-file-validate $(APPENTRIES)/applications/$(APPID).desktop"));

const control = read("debian/control");
assert(control.includes("desktop-file-utils"));
assert(control.includes("/opt/apps/cn.labelnova.app"));

const cmake = read("qt-shell/CMakeLists.txt");
assert(!cmake.includes('DESTINATION share/applications'));
assert(!cmake.includes('DESTINATION share/icons/hicolor/scalable/apps'));
assert(cmake.includes('install(FILES "${FRONTEND_ROOT}/index.html" DESTINATION frontend)'));

assert.strictEqual(
  fs.existsSync(path.join(repoRoot, "debian/install")),
  false,
  "debian/install should be removed once debian/rules owns runtime staging"
);
```

- [ ] **Step 2: Run the test to verify it fails**

Run:

```bash
node tests/debian-store-layout.test.js
```

Expected: FAIL because `debian/rules`, `debian/control`, `qt-shell/CMakeLists.txt`, and `debian/install` still reflect the old `/usr` layout.

- [ ] **Step 3: Write the minimal implementation**

Replace `debian/control` with:

```debcontrol
Source: labelnova
Section: utils
Priority: optional
Maintainer: Hao <hao@example.invalid>
Build-Depends:
 debhelper-compat (= 13),
 cmake,
 pkgconf,
 desktop-file-utils,
 libavcodec-dev,
 libavformat-dev,
 qt6-base-dev | qtbase5-dev,
 qt6-webchannel-dev | libqt5webchannel5-dev | qtwebchannel5-dev,
 qt6-webengine-dev | qtwebengine5-dev
Build-Conflicts:
 libavcodec-extra58,
 libavformat-extra58
Standards-Version: 4.7.0
Homepage: https://mm.md/p/
Rules-Requires-Root: no

Package: labelnova
Architecture: any
Depends:
 ${misc:Depends},
 ${shlibs:Depends},
 libcups2
Description: LabelNova desktop shell packaged for the deepin/UOS store layout
 LabelNova wraps the existing LabelTender frontend in a Qt WebEngine shell.
 It installs its runnable payload under /opt/apps/cn.labelnova.app and
 provides native file dialogs, printer discovery, and CUPS-backed printing.
```

Replace `debian/rules` with:

```make
#!/usr/bin/make -f

include /usr/share/dpkg/architecture.mk
include /usr/share/dpkg/pkg-info.mk

BUILDDIR := obj-$(DEB_HOST_GNU_TYPE)
APPID := cn.labelnova.app
APPROOT := $(CURDIR)/debian/labelnova/opt/apps/$(APPID)
APPFILES := $(APPROOT)/files
APPENTRIES := $(APPROOT)/entries

export DEB_BUILD_MAINT_OPTIONS = hardening=+all
export QTWEBENGINE_DISABLE_SANDBOX = 1

%:
	dh $@ --buildsystem=cmake

override_dh_auto_configure:
	dh_auto_configure --sourcedirectory=qt-shell --builddirectory=$(BUILDDIR) -- \
		-DCMAKE_BUILD_TYPE=RelWithDebInfo

override_dh_auto_build:
	dh_auto_build --builddirectory=$(BUILDDIR)

override_dh_auto_test:
	:

override_dh_auto_install:
	rm -rf $(APPROOT)
	install -d $(APPFILES)/bin
	install -d $(APPFILES)/frontend
	install -d $(APPENTRIES)/applications
	install -d $(APPENTRIES)/icons/hicolor/scalable/apps
	install -m 0755 $(BUILDDIR)/labelnova $(APPFILES)/bin/labelnova
	cp -a index.html $(APPFILES)/frontend/
	cp -a static $(APPFILES)/frontend/
	cp -a icons $(APPFILES)/frontend/
	install -m 0644 debian/$(APPID).desktop $(APPENTRIES)/applications/$(APPID).desktop
	install -m 0644 qt-shell/packaging/labelnova.svg $(APPENTRIES)/icons/hicolor/scalable/apps/$(APPID).svg
	sed -e 's/@VERSION@/$(DEB_VERSION_UPSTREAM)/g' \
		-e 's/@ARCH@/$(DEB_HOST_ARCH)/g' \
		debian/$(APPID).info.in > $(APPROOT)/info
	desktop-file-validate $(APPENTRIES)/applications/$(APPID).desktop

override_dh_auto_clean:
	dh_auto_clean --sourcedirectory=qt-shell --builddirectory=$(BUILDDIR) || true
	rm -rf qt-shell/build $(BUILDDIR) debian/labelnova/opt
```

Update the install section of `qt-shell/CMakeLists.txt` to this exact block and delete the old `PACKAGING_DIR`, `BUILD_DESKTOP_FILE`, `configure_file(...)`, and desktop/icon install lines:

```cmake
set(FRONTEND_ROOT "${CMAKE_CURRENT_LIST_DIR}/..")

install(TARGETS labelnova RUNTIME DESTINATION bin)
install(FILES "${FRONTEND_ROOT}/index.html" DESTINATION frontend)
install(DIRECTORY "${FRONTEND_ROOT}/static" DESTINATION frontend)
install(DIRECTORY "${FRONTEND_ROOT}/icons" DESTINATION frontend)
```

Remove the old runtime install manifest:

```bash
git rm debian/install
```

- [ ] **Step 4: Run the tests to verify they pass**

Run:

```bash
node tests/debian-store-layout.test.js
node --check tests/debian-store-layout.test.js
```

Expected: both commands exit `0`, and the first command prints `debian store layout tests passed`.

- [ ] **Step 5: Commit**

Run:

```bash
git add tests/debian-store-layout.test.js debian/control debian/rules qt-shell/CMakeLists.txt
git commit -m "feat: switch deb packaging to deepin uos store layout"
```

### Task 3: Refresh The Debian Revision, Build The Source Package, And Upload To OBS

**Files:**
- Modify: `debian/changelog`
- Verify: `tests/debian-store-layout.test.js`
- Verify: generated source-package files in `<workspace>`

- [ ] **Step 1: Add the changelog entry**

Prepend this entry to `debian/changelog`, replacing the timestamp with the current local time when you execute it:

```debchangelog
labelnova (1.0.2-2) unstable; urgency=medium

  * Repackage the desktop shell for the deepin/UOS /opt/apps store layout.

 -- Hao <hao@example.invalid>  Mon, 20 Apr 2026 16:30:00 +0800
```

- [ ] **Step 2: Run the local verification commands**

Run:

```bash
node tests/debian-store-layout.test.js
node tests/label-templates.test.js
node tests/object-placement.test.js
node tests/desktop-runtime.test.js
dpkg-buildpackage -S -sa -d -us -uc
```

Expected:

- all `node` commands exit `0`
- `dpkg-buildpackage` exits `0`
- the files `<workspace>/labelnova_1.0.2-2.dsc`, `<workspace>/labelnova_1.0.2-2.debian.tar.xz`, and `<workspace>/labelnova_1.0.2.orig.tar.xz` exist

- [ ] **Step 3: Verify the built source package contains the new packaging assets**

Run:

```bash
tar -tf <workspace>/labelnova_1.0.2-2.debian.tar.xz | rg 'debian/(cn.labelnova.app.desktop|cn.labelnova.app.info.in|rules|control)$'
```

Expected:

```text
debian/cn.labelnova.app.desktop
debian/cn.labelnova.app.info.in
debian/control
debian/rules
```

- [ ] **Step 4: Upload the refreshed source package to OBS**

Run:

```bash
rm -rf /tmp/obs-home-admin-app
mkdir -p /tmp/obs-home-admin-app
cd /tmp/obs-home-admin-app
osc checkout home:Admin:app labelnova
cp <workspace>/labelnova_1.0.2-2.dsc /tmp/obs-home-admin-app/labelnova/
cp <workspace>/labelnova_1.0.2-2.debian.tar.xz /tmp/obs-home-admin-app/labelnova/
cp <workspace>/labelnova_1.0.2.orig.tar.xz /tmp/obs-home-admin-app/labelnova/
cd /tmp/obs-home-admin-app/labelnova
osc addremove
osc ci -m "Switch labelnova to the deepin UOS /opt/apps store layout"
```

Expected: `osc ci` exits `0` and prints a new committed revision for `home:Admin:app/labelnova`.

- [ ] **Step 5: Commit**

Run:

```bash
git add debian/changelog
git commit -m "chore: bump package revision for deepin uos store upload"
```

## Self-Review Checklist

- Spec coverage:
  - `/opt/apps/<appid>` layout is implemented in Task 2.
  - `entries/applications`, `entries/icons`, and `info` metadata are covered in Tasks 1 and 2.
  - OBS source-package rebuild and upload are covered in Task 3.
- Placeholder scan:
  - no `TODO`, `TBD`, or “similar to above” shortcuts remain
  - every changed file has an exact path
  - every code-changing step includes the concrete content to write
- Type and path consistency:
  - one canonical app ID: `cn.labelnova.app`
  - one canonical runtime root: `/opt/apps/cn.labelnova.app`
  - launcher, icon, and `info` metadata all use the same ID
