Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
aca004c513 | ||
|
|
5638c56558 |
@@ -12,7 +12,7 @@ This file is mandatory for AI and coding agents working in this repository. Read
|
|||||||
|
|
||||||
## Product identity and non-goals
|
## Product identity and non-goals
|
||||||
|
|
||||||
- Web Tools is a self-hosted Vue 3 + TypeScript browser-local tools application with a zero-dependency Node.js static/configuration server.
|
- Web Tools is an anonymous-access, self-hosted Vue 3 + TypeScript browser-local tools application with a zero-dependency Node.js static/configuration server.
|
||||||
- The default production port is `9000`. Port `8081` is only the local Vite development proxy target.
|
- The default production port is `9000`. Port `8081` is only the local Vite development proxy target.
|
||||||
- The application is anonymous. Do not add login, accounts, favorites, sponsorship, analytics, telemetry, advertising, CDN assets, or a database.
|
- The application is anonymous. Do not add login, accounts, favorites, sponsorship, analytics, telemetry, advertising, CDN assets, or a database.
|
||||||
- Tool inputs, keys, files, images, and results must remain inside the current browser tab. Do not send them to the Node server, a third-party API, or any remote service.
|
- Tool inputs, keys, files, images, and results must remain inside the current browser tab. Do not send them to the Node server, a third-party API, or any remote service.
|
||||||
@@ -64,6 +64,7 @@ This file is mandatory for AI and coding agents working in this repository. Read
|
|||||||
- Do not add a visible link to `/setting`, explanatory marketing copy, login UI, favorites, or sponsorship UI.
|
- Do not add a visible link to `/setting`, explanatory marketing copy, login UI, favorites, or sponsorship UI.
|
||||||
- Preserve the application icon contract under `public/`: `favicon.svg`, a 16/32/48-frame `favicon.ico`, `apple-touch-icon.png` at 180x180, `icon-192.png`, `icon-512.png`, `icon-maskable-512.png`, and `site.webmanifest`. Keep the icon visually aligned with the sidebar brand and keep the maskable artwork inside its safe zone.
|
- Preserve the application icon contract under `public/`: `favicon.svg`, a 16/32/48-frame `favicon.ico`, `apple-touch-icon.png` at 180x180, `icon-192.png`, `icon-512.png`, `icon-maskable-512.png`, and `site.webmanifest`. Keep the icon visually aligned with the sidebar brand and keep the maskable artwork inside its safe zone.
|
||||||
- The manifest is metadata/install-ready only. Do not add a Service Worker, offline cache, install prompt, splash-screen bundle, Safari mask icon, legacy `browserconfig.xml`, redundant Apple sizes, or separate 16/32 PNG favicons unless requirements explicitly change.
|
- The manifest is metadata/install-ready only. Do not add a Service Worker, offline cache, install prompt, splash-screen bundle, Safari mask icon, legacy `browserconfig.xml`, redundant Apple sizes, or separate 16/32 PNG favicons unless requirements explicitly change.
|
||||||
|
- Preserve the share metadata contract: the single user-facing description is exactly `一个匿名访问、自托管的浏览器本地工具箱`, represented by the standard HTML description and the identical `og:description` compatibility field. Do not add a custom share image, other Open Graph or platform-specific metadata, platform crawlers, route-specific previews, tool lists, or user data. Sharing clients may fall back to the document title and existing favicon assets, but their final preview and cache behavior remain platform-controlled.
|
||||||
- For UI work, verify a wide desktop, an intermediate width around 801-1040 px, and mobile portrait around 390 x 844 in a real browser. Check direct routes, refresh, navigation, search, `/setting`, both themes, console errors, and network requests.
|
- For UI work, verify a wide desktop, an intermediate width around 801-1040 px, and mobile portrait around 390 x 844 in a real browser. Check direct routes, refresh, navigation, search, `/setting`, both themes, console errors, and network requests.
|
||||||
|
|
||||||
## Adding a tool
|
## Adding a tool
|
||||||
|
|||||||
@@ -2,6 +2,21 @@
|
|||||||
|
|
||||||
本文档记录面向使用者和维护者的重要变更。版本号以 `package.json` 为准。
|
本文档记录面向使用者和维护者的重要变更。版本号以 `package.json` 为准。
|
||||||
|
|
||||||
|
## 0.1.0 - 2026-08-09
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- 增加统一页面介绍,不列出具体工具或生成路由专属内容。
|
||||||
|
|
||||||
|
### Documentation
|
||||||
|
|
||||||
|
- 明确标准 HTML description 和同文的 `og:description` 分享兼容性契约,不设置自定义分享图、其他 Open Graph、平台抓取器、平台专用字段或包含用户数据的动态内容。
|
||||||
|
|
||||||
|
### Verification
|
||||||
|
|
||||||
|
- 87 条 Vitest 测试通过,页面元数据测试覆盖标准 description、唯一允许的 `og:description`、Manifest 描述、favicon 回退入口和无自定义分享图约束,并增加发布版本跨文件一致性检查。
|
||||||
|
- Vue/TypeScript 类型检查和生产构建通过。
|
||||||
|
|
||||||
## 0.0.6 - 2026-08-08
|
## 0.0.6 - 2026-08-08
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
[简体中文](README.zh-CN.md)
|
[简体中文](README.zh-CN.md)
|
||||||
|
|
||||||
Web Tools is a self-hosted collection of browser-local utilities built with Vue 3, TypeScript, Vite, and a small zero-dependency Node.js server. It supports multiple simultaneous users: text, keys, files, images, and calculation results remain inside each browser tab. The server only persists the navigation order shared by all visitors.
|
Web Tools is an anonymously accessible, self-hosted collection of browser-local utilities built with Vue 3, TypeScript, Vite, and a small zero-dependency Node.js server. It supports multiple simultaneous users: text, keys, files, images, and calculation results remain inside each browser tab. The server only persists the navigation order shared by all visitors.
|
||||||
|
|
||||||
The authoritative application version is the `version` field in `package.json`. The default production address is `http://localhost:9000`.
|
The authoritative application version is the `version` field in `package.json`. The default production address is `http://localhost:9000`.
|
||||||
|
|
||||||
@@ -112,13 +112,15 @@ server/ static server and navigation configuration API
|
|||||||
shared/tool-catalog.json authoritative tool metadata
|
shared/tool-catalog.json authoritative tool metadata
|
||||||
shared/default-navigation.json first-run navigation order
|
shared/default-navigation.json first-run navigation order
|
||||||
tests/ unit and server integration tests
|
tests/ unit and server integration tests
|
||||||
public/ favicon, Apple touch icon, and Web App Manifest assets
|
public/ browser icons and Web App Manifest
|
||||||
```
|
```
|
||||||
|
|
||||||
New tools must keep their computation independent, register the same stable ID in the catalog, default navigation, and lazy-load registry, reuse the shared UI, define resource limits where appropriate, and include normal, invalid, boundary, and known-vector tests. Routes are generated from the tool registry; ordinary tools do not add routes manually.
|
New tools must keep their computation independent, register the same stable ID in the catalog, default navigation, and lazy-load registry, reuse the shared UI, define resource limits where appropriate, and include normal, invalid, boundary, and known-vector tests. Routes are generated from the tool registry; ordinary tools do not add routes manually.
|
||||||
|
|
||||||
The browser icon set follows the sidebar brand and includes SVG, multi-frame ICO, Apple touch, 192/512, and maskable assets. The manifest supplies metadata only: the project deliberately has no Service Worker, offline cache, install prompt, or splash-screen bundle, so a deployment update cannot be hidden by application-managed stale caches.
|
The browser icon set follows the sidebar brand and includes SVG, multi-frame ICO, Apple touch, 192/512, and maskable assets. The manifest supplies metadata only: the project deliberately has no Service Worker, offline cache, install prompt, or splash-screen bundle, so a deployment update cannot be hidden by application-managed stale caches.
|
||||||
|
|
||||||
|
The page exposes one product description through both the standard HTML description and an identical `og:description` compatibility field. It has no custom share image, other Open Graph metadata, platform crawler, or platform-specific fields. Sharing clients may fall back to the document title and existing favicons, but each platform controls its final preview and cache refresh behavior.
|
||||||
|
|
||||||
For the complete extension procedure and AI handoff rules, read [AGENTS.md](AGENTS.md) and [README.zh-CN.md](README.zh-CN.md) before editing. Inspect the Git worktree first and preserve existing changes.
|
For the complete extension procedure and AI handoff rules, read [AGENTS.md](AGENTS.md) and [README.zh-CN.md](README.zh-CN.md) before editing. Inspect the Git worktree first and preserve existing changes.
|
||||||
|
|
||||||
## Documentation
|
## Documentation
|
||||||
|
|||||||
+6
-4
@@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
一个匿名访问、自托管的浏览器本地工具箱。支持多人同时使用;除全局导航顺序外,文本、密钥、文件、图片和计算结果均只存在于各自浏览器中,不会发送到服务端。
|
一个匿名访问、自托管的浏览器本地工具箱。支持多人同时使用;除全局导航顺序外,文本、密钥、文件、图片和计算结果均只存在于各自浏览器中,不会发送到服务端。
|
||||||
|
|
||||||
当前应用版本:`0.0.6`。
|
当前应用版本:`0.1.0`。
|
||||||
|
|
||||||
## 内置工具
|
## 内置工具
|
||||||
|
|
||||||
@@ -35,7 +35,7 @@ shared/tool-catalog.json 工具与分类的权威元数据目录
|
|||||||
shared/default-navigation.json 新部署的默认分类和工具顺序
|
shared/default-navigation.json 新部署的默认分类和工具顺序
|
||||||
tests/ Vitest 单元测试
|
tests/ Vitest 单元测试
|
||||||
dist/ npm run build 生成的生产前端
|
dist/ npm run build 生成的生产前端
|
||||||
public/ 浏览器标签页、Apple 主屏幕和 Web App Manifest 图标资源
|
public/ 浏览器图标和 Web App Manifest
|
||||||
Dockerfile 多阶段容器构建
|
Dockerfile 多阶段容器构建
|
||||||
docker-compose.yml 默认部署配置
|
docker-compose.yml 默认部署配置
|
||||||
THIRD_PARTY_NOTICES.md 第三方依赖许可说明
|
THIRD_PARTY_NOTICES.md 第三方依赖许可说明
|
||||||
@@ -59,6 +59,8 @@ CHANGELOG.md 版本变更、修复和验证记录
|
|||||||
|
|
||||||
`site.webmanifest` 只提供应用名称、颜色和图标元数据,便于浏览器添加到主屏幕。项目不注册 Service Worker,不启用离线缓存、安装提示或启动画面,避免部署更新后继续加载过期构建。
|
`site.webmanifest` 只提供应用名称、颜色和图标元数据,便于浏览器添加到主屏幕。项目不注册 Service Worker,不启用离线缓存、安装提示或启动画面,避免部署更新后继续加载过期构建。
|
||||||
|
|
||||||
|
页面只提供统一介绍“一个匿名访问、自托管的浏览器本地工具箱”,同时写入标准 HTML description 和同文的 `og:description`,以兼容不同分享客户端;不设置自定义分享图片、其他 Open Graph 或平台专用字段,也不增加平台抓取器。分享平台可以回退使用页面标题和现有 favicon,但最终展示及缓存刷新时机由平台决定。
|
||||||
|
|
||||||
## 核心功能契约
|
## 核心功能契约
|
||||||
|
|
||||||
以下行为属于已经发布的兼容性约束。修改时必须同步实现、页面提示、自动化测试和本文档,不能只调整界面文案。
|
以下行为属于已经发布的兼容性约束。修改时必须同步实现、页面提示、自动化测试和本文档,不能只调整界面文案。
|
||||||
@@ -243,10 +245,10 @@ docker compose restart web-tools
|
|||||||
|
|
||||||
## 离线镜像
|
## 离线镜像
|
||||||
|
|
||||||
若交付目录包含 `personal-web-tools-0.0.6-amd64.tar`,可直接导入:
|
若交付目录包含 `personal-web-tools-0.1.0-amd64.tar`,可直接导入:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
docker load -i personal-web-tools-0.0.6-amd64.tar
|
docker load -i personal-web-tools-0.1.0-amd64.tar
|
||||||
docker compose up -d --no-build
|
docker compose up -d --no-build
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
services:
|
services:
|
||||||
web-tools:
|
web-tools:
|
||||||
image: personal-web-tools:0.0.6
|
image: personal-web-tools:0.1.0
|
||||||
build:
|
build:
|
||||||
context: .
|
context: .
|
||||||
dockerfile: Dockerfile
|
dockerfile: Dockerfile
|
||||||
|
|||||||
+2
-1
@@ -4,7 +4,8 @@
|
|||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<meta name="theme-color" content="#f5f6f7" />
|
<meta name="theme-color" content="#f5f6f7" />
|
||||||
<meta name="description" content="浏览器本地运行的个人 Web 工具箱" />
|
<meta name="description" content="一个匿名访问、自托管的浏览器本地工具箱" />
|
||||||
|
<meta property="og:description" content="一个匿名访问、自托管的浏览器本地工具箱" />
|
||||||
<link rel="icon" href="/favicon.ico" sizes="16x16 32x32 48x48" />
|
<link rel="icon" href="/favicon.ico" sizes="16x16 32x32 48x48" />
|
||||||
<link rel="icon" href="/favicon.svg" type="image/svg+xml" sizes="any" />
|
<link rel="icon" href="/favicon.svg" type="image/svg+xml" sizes="any" />
|
||||||
<link rel="apple-touch-icon" href="/apple-touch-icon.png" sizes="180x180" />
|
<link rel="apple-touch-icon" href="/apple-touch-icon.png" sizes="180x180" />
|
||||||
|
|||||||
Generated
+2
-2
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "personal-web-tools",
|
"name": "personal-web-tools",
|
||||||
"version": "0.0.6",
|
"version": "0.1.0",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "personal-web-tools",
|
"name": "personal-web-tools",
|
||||||
"version": "0.0.6",
|
"version": "0.1.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@lucide/vue": "^1.29.0",
|
"@lucide/vue": "^1.29.0",
|
||||||
"@stablelib/aes": "^2.0.1",
|
"@stablelib/aes": "^2.0.1",
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "personal-web-tools",
|
"name": "personal-web-tools",
|
||||||
"version": "0.0.6",
|
"version": "0.1.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"engines": {
|
"engines": {
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "Web Tools",
|
"name": "Web Tools",
|
||||||
"short_name": "Web Tools",
|
"short_name": "Web Tools",
|
||||||
"description": "浏览器本地运行的个人 Web 工具箱",
|
"description": "一个匿名访问、自托管的浏览器本地工具箱",
|
||||||
"start_url": "/",
|
"start_url": "/",
|
||||||
"scope": "/",
|
"scope": "/",
|
||||||
"display": "standalone",
|
"display": "standalone",
|
||||||
|
|||||||
+15
-1
@@ -67,7 +67,7 @@ function decodeRgbaPng(buffer) {
|
|||||||
return { width, height, pixels }
|
return { width, height, pixels }
|
||||||
}
|
}
|
||||||
|
|
||||||
describe('application icon assets', () => {
|
describe('application branding assets', () => {
|
||||||
it('keeps the vector favicon self-contained', async () => {
|
it('keeps the vector favicon self-contained', async () => {
|
||||||
const svg = await readFile(path.join(publicRoot, 'favicon.svg'), 'utf8')
|
const svg = await readFile(path.join(publicRoot, 'favicon.svg'), 'utf8')
|
||||||
|
|
||||||
@@ -128,6 +128,7 @@ describe('application icon assets', () => {
|
|||||||
expect(manifest).toMatchObject({
|
expect(manifest).toMatchObject({
|
||||||
name: 'Web Tools',
|
name: 'Web Tools',
|
||||||
short_name: 'Web Tools',
|
short_name: 'Web Tools',
|
||||||
|
description: '一个匿名访问、自托管的浏览器本地工具箱',
|
||||||
start_url: '/',
|
start_url: '/',
|
||||||
scope: '/',
|
scope: '/',
|
||||||
display: 'standalone',
|
display: 'standalone',
|
||||||
@@ -151,6 +152,19 @@ describe('application icon assets', () => {
|
|||||||
expect(html).toContain('<link rel="manifest" href="/site.webmanifest" />')
|
expect(html).toContain('<link rel="manifest" href="/site.webmanifest" />')
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('declares one product description for standard and Open Graph consumers without a custom image', async () => {
|
||||||
|
const html = (await readProjectFile('index.html')).toString('utf8')
|
||||||
|
const socialMetadata = [...html.matchAll(/<meta\s+(?:name|property)="((?:og|twitter):[^"]+)"/gi)]
|
||||||
|
.map((match) => match[1].toLowerCase())
|
||||||
|
|
||||||
|
expect(html).toContain('<meta name="description" content="一个匿名访问、自托管的浏览器本地工具箱" />')
|
||||||
|
expect(html).toContain('<meta property="og:description" content="一个匿名访问、自托管的浏览器本地工具箱" />')
|
||||||
|
expect(html.match(/一个匿名访问、自托管的浏览器本地工具箱/g)).toHaveLength(2)
|
||||||
|
expect(socialMetadata).toEqual(['og:description'])
|
||||||
|
expect(html).not.toContain('share-preview.png')
|
||||||
|
await expect(access(path.join(publicRoot, 'share-preview.png'))).rejects.toMatchObject({ code: 'ENOENT' })
|
||||||
|
})
|
||||||
|
|
||||||
it('includes public assets in container builds', async () => {
|
it('includes public assets in container builds', async () => {
|
||||||
expect((await readProjectFile('Dockerfile')).toString('utf8')).toContain('COPY public ./public')
|
expect((await readProjectFile('Dockerfile')).toString('utf8')).toContain('COPY public ./public')
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -11,6 +11,27 @@ async function readProjectFile(relativePath) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
describe('deployment configuration', () => {
|
describe('deployment configuration', () => {
|
||||||
|
it('keeps the release version synchronized across project and deployment metadata', async () => {
|
||||||
|
const [packageText, lockText, compose, readme, changelog] = await Promise.all([
|
||||||
|
readProjectFile('package.json'),
|
||||||
|
readProjectFile('package-lock.json'),
|
||||||
|
readProjectFile('docker-compose.yml'),
|
||||||
|
readProjectFile('README.zh-CN.md'),
|
||||||
|
readProjectFile('CHANGELOG.md'),
|
||||||
|
])
|
||||||
|
const packageJson = JSON.parse(packageText)
|
||||||
|
const packageLock = JSON.parse(lockText)
|
||||||
|
const version = packageJson.version
|
||||||
|
|
||||||
|
expect(version).toMatch(/^\d+\.\d+\.\d+$/)
|
||||||
|
expect(packageLock.version).toBe(version)
|
||||||
|
expect(packageLock.packages[''].version).toBe(version)
|
||||||
|
expect(compose.match(/^\s*image:\s*personal-web-tools:(\S+)$/m)?.[1]).toBe(version)
|
||||||
|
expect(readme).toContain(`当前应用版本:\`${version}\`。`)
|
||||||
|
expect(readme).toContain(`personal-web-tools-${version}-amd64.tar`)
|
||||||
|
expect(changelog.match(/^## (\d+\.\d+\.\d+) - /m)?.[1]).toBe(version)
|
||||||
|
})
|
||||||
|
|
||||||
it('keeps the default production port synchronized', async () => {
|
it('keeps the default production port synchronized', async () => {
|
||||||
const [dockerfile, compose, environment, script, readme] = await Promise.all([
|
const [dockerfile, compose, environment, script, readme] = await Promise.all([
|
||||||
readProjectFile('Dockerfile'),
|
readProjectFile('Dockerfile'),
|
||||||
|
|||||||
Reference in New Issue
Block a user