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

15 KiB
Raw Permalink Blame History

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

{
  "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)

{}

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)

#!/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)

#!/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).