Files
q-blog/docs/AUDIT_REPORT_v0.0.1.md
greenflame089 0b100af686 Release v0.2.2
2025-08-22 07:28:42 -04:00

453 lines
15 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Q-Blog v0.0.1 — Source Audit
_Generated: 2025-08-21T19:40:03_
## Inventory Summary
- Extracted to: `/mnt/data/q-blog_v0.0.1_source`
- Total files: **257**
- Total size: **2.47 MB**
- Top-level directories:
- src: 121 files
- scripts: 42 files
- docs: 31 files
- (root): 21 files
- issues: 15 files
- reports: 12 files
- .gitea: 5 files
- tests: 5 files
- pr: 4 files
- public: 1 files
- File types (top 20):
- .tsx: 76
- .md: 59
- .sh: 40
- .ts: 33
- .json: 9
- .txt: 9
- .png: 8
- (noext): 5
- .ttf: 5
- .css: 2
- .js: 2
- .svg: 2
- .bak: 1
- .html: 1
- .ico: 1
- .log: 1
- .mjs: 1
- .yaml: 1
- .yml: 1
Inventory CSV: `/mnt/data/q-blog_inventory_v0.0.1.csv`
## Key Files Presence
- package.json: ✅
- pnpm-lock.yaml: ❌
- yarn.lock: ❌
- package-lock.json: ✅
- tsconfig.json: ✅
- vite.config.ts: ✅
- vitest.config.ts: ✅
- jest.config.js: ❌
- README.md: ❌
- docs/ARCHITECTURE.md: ❌
- docs/TESTING.md: ✅
- docs/ACCESSIBILITY.md: ❌
- docs/SECURITY.md: ❌
- docs/USER_JOURNEYS.md: ✅
- docs/GLOSSARY.md: ❌
- docs/ROADMAP_DEPENDENCIES.md: ✅
- docs/DECISIONS/README.md: ❌
- src/main.tsx: ✅
- src/App.tsx: ✅
- src/index.css: ✅
- src/routes.tsx: ❌
- src/router.tsx: ❌
- src/components/BlogSwitcher.tsx: ❌
- src/components/PostEditor.tsx: ❌
- src/components/PostList.tsx: ❌
- src/components/MembersPanel.tsx: ❌
- src/components/HeaderNav.tsx: ❌
- src/store/index.ts: ❌
- src/store/api.ts: ❌
- src/store/slices/postsSlice.ts: ❌
- src/store/slices/blogsSlice.ts: ❌
- src/store/slices/authSlice.ts: ❌
- src/i18n.ts: ❌
- src/theme.ts: ❌
- tests/setup.ts: ✅
- tests/App.test.tsx: ❌
- tests/components/PostEditor.test.tsx: ❌
- scripts/release/create-gitea-release.sh: ✅
- scripts/release/build-archive.sh: ✅
- docs/RELEASE_NOTES_v0.0.1.md: ✅
- .gitea/workflows/ci.yml: ✅
- .gitea/workflows/release.yml: ❌
## package.json Snapshot
```json
{
"name": "q-blog",
"version": "0.0.1",
"scripts": {
"dev": "vite",
"build": "tsc && vite build",
"preview": "vite preview",
"lint": "eslint .",
"format": "prettier --check .",
"format:write": "prettier --write .",
"test": "vitest",
"test:run": "vitest run",
"typecheck": "tsc -p tsconfig.json -noEmit",
"lint:phase0": "LINT_SCOPE=phase0 eslint .",
"lint:full": "LINT_SCOPE=full eslint .",
"check": "npm run typecheck && npm run lint:phase0 && npm run test:run",
"scan:phase1": "bash scripts/dev/phase1-scan.sh",
"report:phase1": "bash scripts/dev/phase1-report.sh"
},
"dependencies": {
"@emotion/react": "^11.10.6",
"@emotion/styled": "^11.10.6",
"@mui/icons-material": "^5.11.11",
"@mui/material": "^5.11.13",
"@reduxjs/toolkit": "^1.9.3",
"@types/react-grid-layout": "^1.3.2",
"axios": "^1.3.4",
"compressorjs": "^1.2.1",
"localforage": "^1.10.0",
"moment": "^2.29.4",
"philliplm-react-modern-audio-player": "^1.4.6",
"react": "^18.2.0",
"react-copy-to-clipboard": "^5.1.0",
"react-dnd": "^16.0.1",
"react-dnd-html5-backend": "^16.0.1",
"react-dom": "^18.2.0",
"react-dropzone": "^14.2.3",
"react-grid-layout": "^1.3.4",
"react-intersection-observer": "^9.4.3",
"react-masonry-css": "^1.0.16",
"react-redux": "^8.0.5",
"react-resize-detector": "^8.0.4",
"react-router-dom": "^6.9.0",
"react-toastify": "^9.1.2",
"react-virtuoso": "^4.3.3",
"short-unique-id": "^4.4.4",
"slate": "^0.91.4",
"slate-history": "^0.86.0",
"slate-react": "^0.91.11",
"ts-key-enum": "^2.0.12"
},
"devDependencies": {
"@eslint/js": "^9.33.0",
"@mui/types": "^7.2.3",
"@testing-library/jest-dom": "^6.7.0",
"@testing-library/react": "^16.3.0",
"@testing-library/user-event": "^14.6.1",
"@types/node": "^24.3.0",
"@types/react": "^18.3.23",
"@types/react-copy-to-clipboard": "^5.0.4",
"@types/react-dom": "^18.3.7",
"@vitejs/plugin-react-swc": "^3.2.0",
"@vitest/coverage-v8": "^3.2.4",
"axe-core": "^4.10.3",
"eslint": "^9.33.0",
"eslint-config-prettier": "^10.1.8",
"eslint-plugin-jsx-a11y": "^6.10.2",
"eslint-plugin-react-hooks": "^5.2.0",
"jest-axe": "^10.0.0",
"jsdom": "^26.1.0",
"msw": "^2.10.5",
"prettier": "^2.8.8",
"typescript": "^4.9.5",
"typescript-eslint": "^8.39.1",
"vite": "^4.2.0",
"vitest": "^3.2.4",
"worker-loader": "^3.0.8"
}
}
```
## tsconfig.json (key fields)
```json
{}
```
## Tooling Signals in Source
- redux_toolkit: 6 files
- rtk_query: 0 files
- slate: 6 files
- mui: 51 files
- i18n: 0 files
- vite: 0 files
- dompurify: 0 files
- zod: 0 files
- jest_axe: 0 files
- msw: 0 files
- rtl: 0 files
- virtualize: 0 files
- sample `mui`: src/App.tsx, src/pages/BlogIndividualPost/BlogIndividualPost.tsx, src/pages/BlogIndividualProfile/BlogIndividualProfile.tsx, src/pages/CreatePost/CreatePost.tsx, src/pages/CreatePost/CreatePostMinimal.tsx, src/pages/CreatePost/CreatePost-styles.ts ...
- sample `slate`: src/pages/CreatePost/CreatePostMinimal.tsx, src/pages/CreatePost/CreatePostBuilder.tsx, src/pages/EditPost/EditPost.tsx, src/components/editor/BlogEditor.tsx, src/components/editor/ReadOnlySlate.tsx, src/components/editor/customTypes.ts
- sample `redux_toolkit`: src/state/store.ts, src/state/features/globalSlice.ts, src/state/features/blogSlice.ts, src/state/features/authSlice.ts, src/state/features/mailSlice.ts, src/state/features/notificationsSlice.ts
## Routes & Landmarks (heuristic)
- src/main.tsx: h1=0, header=False, nav=False, main=False, footer=False
- src/App.tsx: h1=0, header=False, nav=False, main=False, footer=False
- src/pages/BlogIndividualPost/BlogIndividualPost.tsx: h1=0, header=False, nav=False, main=False, footer=False
- src/pages/BlogIndividualProfile/BlogIndividualProfile.tsx: h1=0, header=False, nav=False, main=False, footer=False
- src/pages/CreatePost/CreatePost.tsx: h1=0, header=False, nav=False, main=False, footer=False
- src/pages/CreatePost/CreatePostMinimal.tsx: h1=0, header=False, nav=False, main=False, footer=False
- src/pages/CreatePost/CreatePost-styles.ts: h1=0, header=False, nav=False, main=False, footer=False
- src/pages/CreatePost/CreatePostBuilder.tsx: h1=0, header=False, nav=False, main=False, footer=False
- src/pages/CreatePost/components/Toolbar/EditorToolbar.tsx: h1=0, header=False, nav=False, main=False, footer=False
- src/pages/CreatePost/components/Navbar/NavbarBuilder.tsx: h1=0, header=False, nav=False, main=False, footer=False
- src/pages/EditPost/EditPost.tsx: h1=0, header=False, nav=False, main=False, footer=False
- src/pages/Home/Home.tsx: h1=0, header=False, nav=False, main=False, footer=False
- src/pages/CreateEditProfile/CreatEditProfile.tsx: h1=0, header=False, nav=False, main=False, footer=False
- src/pages/BlogList/PostPreview-styles.ts: h1=0, header=False, nav=False, main=False, footer=False
- src/pages/BlogList/PostPreview.tsx: h1=0, header=False, nav=False, main=False, footer=False
- src/pages/BlogList/BlogList.tsx: h1=0, header=False, nav=False, main=False, footer=False
## Release Scripts — quick notes
### scripts/release/create-gitea-release.sh (excerpt)
```bash
#!/usr/bin/env bash
set -euo pipefail
# create-gitea-release.sh
# Creates/updates a Gitea release for a given version/tag and uploads zips from release/.
#
# Env required: GITEA_BASE_URL, GITEA_TOKEN, OWNER, REPO
# Usage:
# bash scripts/release/create-gitea-release.sh 0.0.1 \
# --title "Phase 0 — v0.0.1" \
# --notes docs/RELEASE_NOTES_v0.0.1.md \
# --branch update \
# [--draft] [--prerelease] [--assets 'release/*.zip']
#
# Defaults:
# title: "v<version>"
# notes: docs/RELEASE_NOTES_v<version>.md if exists, else empty
# branch: current git branch (fallback: main)
# assets: release/*.zip
#
# Requires: curl, jq
require() { command -v "$1" >/dev/null 2>&1 || { echo "Missing required command: $1" >&2; exit 1; }; }
require curl
require jq
: "${GITEA_BASE_URL:?Set GITEA_BASE_URL (e.g., https://gitea.example.com)}"
: "${GITEA_TOKEN:?Set GITEA_TOKEN}"
: "${OWNER:?Set OWNER}"
: "${REPO:?Set REPO}"
if [[ $# -lt 1 ]]; then
echo "Usage: $0 <version|vX.Y.Z> [--title TITLE] [--notes FILE] [--branch BRANCH] [--draft] [--prerelease] [--assets GLOB]" >&2
exit 2
fi
VER_RAW="$1"; shift
TAG="${VER_RAW}"
[[ "${TAG}" != v* ]] && TAG="v${TAG}"
TITLE="${TAG}"
NOTES_FILE=""
BRANCH="$(git rev-parse --abbrev-ref HEAD 2>/dev/null || echo main)"
DRAFT=false
PRERELEASE=false
ASSETS_GLOB="release/*.zip"
while [[ $# -gt 0 ]]; do
case "$1" in
--title) TITLE="${2:-${TITLE}}"; shift 2;;
--notes) NOTES_FILE="${2:-}"; shift 2;;
--branch) BRANCH="${2:-${BRANCH}}"; shift 2;;
--draft) DRAFT=true; shift;;
--prerelease) PRERELEASE=true; shift;;
--assets) ASSETS_GLOB="${2:-${ASSETS_GLOB}}"; shift 2;;
*) echo "Unknown arg: $1" >&2; exit 2;;
esac
done
if [[ -z "${NOTES_FILE}" ]]; then
CANDIDATE="docs/RELEASE_NOTES_${TAG}.md"
if [[ -f "${CANDIDATE}" ]]; then
NOTES_FILE="${CANDIDATE}"
else
CANDIDATE="docs/RELEASE_NOTES_${TAG#v}.md"
[[ -f "${CANDIDATE}" ]] && NOTES_FILE="${CANDIDATE}" || NOTES_FILE=""
fi
fi
BODY=""
if [[ -n "${NOTES_FILE}" && -f "${NOTES_FILE}" ]]; then
BODY="$(cat "${NOTES_FILE}")"
fi
API="${GITEA_BASE_URL%/}/api/v1"
auth() { curl -sS -H "Authorization: token ${GITEA_TOKEN}" -H "Content-Type: application/json"; }
auth_up() { curl -sS -H "Authorization: token ${GITEA_TOKEN}"; }
echo "== Looking up release by tag ${TAG} =="
GET_URL="${API}/repos/${OWNER}/${REPO}/releases/tags/${TAG}"
set +e
EXIST_JSON="$(auth GET "${GET_URL}" 2>/dev/null)"
RC=$?
set -e
RELEASE_ID=""
if [[ ${RC} -eq 0 && -n "${EXIST_JSON}" && "$(echo "${EXIST_JSON}" | jq -r '.id // empty')" != "" ]]; then
RELEASE_ID="$(echo "${EXIST_JSON}" | jq -r '.id')"
echo "Found existing release id=${RELEASE_ID}; will PATCH"
PAYLOAD="$(jq -n \
--arg name "${TITLE}" \
--arg body "${BODY}" \
--argjson draft ${DRAFT} \
--argjson prerelease ${PRERELEASE} \
'{name:$name, body:$body, draft:$draft, prerelease:$prerelease}')"
REL_JSON="$(echo "${PAYLOAD}" | auth -X PATCH "${API}/repos/${OWNER}/${REPO}/releases/${RELEASE_ID}" -d @-)"
else
echo "No existing release; creating"
PAYLOAD="$(jq -n \
--arg tag_name "${TAG}" \
--arg target_commitish "${BRANCH}" \
--arg name "${TITLE}" \
--arg body "${BODY}" \
--argjson draft ${DRAFT} \
--argjson prerelease ${PRERELEASE} \
'{tag_name:$tag_name, target_commitish:$target_commitish, name:$name, body:$body, draft:$draft, prerelease:$prerelease}')"
REL_JSON="$(echo "${PAYLOAD}" | auth -X POST "${API}/repos/${OWNER}/${REPO}/releases" -d @-)"
RELEASE_ID="$(echo "${REL_JSON}" | jq -r '.id // empty')"
fi
if [[ -z "${RELEASE_ID}" ]]; then
echo "ERROR: could not determine release id" >&2
echo "${REL_JSON:-"(no server response)"}" >&2
exit 1
fi
HTML_URL="$(echo "${REL_JSON}" | jq -r '.html_url // .url // empty')"
echo "Release ready (id=${RELEASE_ID}) ${HTML_URL}"
echo "== Uploading assets from ${ASSETS_GLOB} =="
```
### scripts/release/build-archive.sh (excerpt)
```bash
#!/usr/bin/env bash
set -euo pipefail
# build-archive.sh
# Creates source zip (always) and an optional dist zip if --with-build succeeds.
# Usage:
# bash scripts/release/build-archive.sh [--with-build] [--outdir release] [--name q-blog]
#
# Notes:
# - By default, NO TypeScript build is executed to avoid failing on app code during Phase 0.
# - Pass --with-build to attempt `npm ci` + `npm run build`; if dist/ exists afterwards, a dist zip is created.
WITH_BUILD=0
OUTDIR="release"
NAME=""
while [[ $# -gt 0 ]]; do
case "$1" in
--with-build) WITH_BUILD=1; shift;;
--outdir) OUTDIR="${2:-release}"; shift 2;;
--name) NAME="${2:-}"; shift 2;;
*) echo "Unknown arg: $1" >&2; exit 2;;
esac
done
require() { command -v "$1" >/dev/null 2>&1 || { echo "Missing required command: $1" >&2; exit 1; }; }
require jq
require zip
if [[ -z "${NAME}" ]]; then
if [[ -f package.json ]]; then
NAME="$(jq -r '.name // empty' package.json)"
fi
[[ -z "${NAME}" ]] && NAME="$(basename "$PWD")"
fi
if [[ -f package.json ]]; then
VERSION="$(jq -r '.version // "0.0.0"' package.json)"
else
VERSION="0.0.0"
fi
mkdir -p "${OUTDIR}"
TAG="v${VERSION}"
SRC_ZIP="${OUTDIR}/${NAME}-${TAG}-src.zip"
DIST_ZIP="${OUTDIR}/${NAME}-${TAG}-dist.zip"
echo "== Creating source archive =="
# Use zip with excludes (works even without git)
zip -q -9 -r "${SRC_ZIP}" . \
-x "node_modules/*" ".git/*" "${OUTDIR}/*" "dist/*" ".vite/*" "coverage/*" "*.log" "*.zip"
if [[ ${WITH_BUILD} -eq 1 ]]; then
echo "== Build step (best-effort) =="
if jq -e '.scripts.build' package.json >/dev/null 2>&1; then
# prefer ci if present, otherwise install
if jq -e '.scripts.ci' package.json >/dev/null 2>&1; then
npm run ci || true
else
(command -v npm >/dev/null 2>&1 && npm ci) || (npm install || true)
fi
npm run build || echo "(build failed or not configured — continuing)"
else
echo "No build script in package.json — skipping build"
fi
if [[ -d dist ]]; then
echo "== Creating dist archive =="
(cd dist && zip -q -9 -r "../${DIST_ZIP##*/}" .)
else
echo "dist/ not found — skipping dist zip."
fi
else
echo "(Skipping build; source zip only. Use --with-build to attempt a build.)"
fi
echo "Artifacts:"
echo " - ${SRC_ZIP}"
[[ -f "${DIST_ZIP}" ]] && echo " - ${DIST_ZIP}"
```
**Potential issues detected:**
- Release script may not validate required args (title/notes); add guards or defaults to avoid empty curl parameters.
## Docs vs Code — spot verification
- README.md mentions Redux Toolkit: ️ code present but not documented
- README.md mentions RTK Query: —
- README.md mentions Slate editor: ️ code present but not documented
- README.md mentions MUI v5: ️ code present but not documented
- README.md mentions i18n: —
- README.md mentions DOMPurify: —
- README.md mentions Zod validation: —
- README.md mentions MSW/jest-axe: —
## Priority Findings & Questions
1. Content sanitization (DOMPurify) not detected in source scan. Verify sanitize-on-save and sanitize-on-render are implemented.
2. Zod validation not detected; confirm validation at form and API boundaries or update docs.
3. Testing stack may be incomplete (MSW/jest-axe). Ensure test coverage for autosave, a11y smoke, and error paths.
4. RTK Query not detected; if used, ensure endpoints are defined with blog-scoped cache keys.
## Next Steps (thin vertical slice)
1. Run `pnpm i` (or npm/yarn) and `pnpm test` to confirm toolchain and a11y smoke tests.
2. Verify PostEditor autosave path, live region announcements, and sanitize pipeline.
3. Resolve release script guard/arg issues; add CI job to create release on tag.
4. Pick a small feature change and map the affected slice (state → API → UI → tests).