forked from Qortal/q-blog
453 lines
15 KiB
Markdown
453 lines
15 KiB
Markdown
# 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).
|