Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
01f7c66
fix(deploy): route root installs to the protected public web entry
ElmanCod110 Oct 19, 2026
87c7d51
fix(assets): set explicit CSS and JavaScript MIME types
ElmanCod110 Oct 19, 2026
834245d
test(ci): syntax-check the production workspace controller
ElmanCod110 Oct 19, 2026
f0cee4d
fix(assets): invalidate stale workspace CSS and JavaScript caches
ElmanCod110 Oct 19, 2026
11187de
fix(styles): refresh design-system layer cache keys
ElmanCod110 Oct 19, 2026
acbc4dd
test(styles): validate production asset wiring instead of showcase demo
ElmanCod110 Oct 19, 2026
aa09247
test(ci): run workspace UI contract assertions in pull requests
ElmanCod110 Oct 19, 2026
c73e080
test(workspace): cover root routing, asset MIME, cache policy, and CI…
ElmanCod110 Oct 19, 2026
ed4b3be
docs(styles): document the production design system without demo assets
ElmanCod110 Oct 19, 2026
c31213d
docs(workspace): document supported web roots and production-only UI
ElmanCod110 Oct 19, 2026
2f0821f
docs(readme): align version and validation commands with production UI
ElmanCod110 Oct 19, 2026
125a281
docs(operations): explain safe root-document-root fallback
ElmanCod110 Oct 19, 2026
5bcbd46
docs(changelog): record asset delivery and production UI cleanup
ElmanCod110 Oct 19, 2026
f63c4d3
refactor(ui): remove standalone visual showcase from product routes
ElmanCod110 Oct 19, 2026
8fc11ca
refactor(styles): remove demo-only showcase stylesheet
ElmanCod110 Oct 19, 2026
f380fab
refactor(ui): remove demo-only showcase script
ElmanCod110 Oct 19, 2026
2a758b3
fix(assets): correct the Apache static-file match expression
ElmanCod110 Oct 19, 2026
57eae34
fix(styles): define missing workspace design-token aliases
ElmanCod110 Oct 19, 2026
80c8ad1
test(styles): guard workspace token aliases against missing variables
ElmanCod110 Oct 19, 2026
3afa684
fix(workspace): connect the team shortcut and remove the fake guide tile
ElmanCod110 Oct 19, 2026
7d2c56c
style(workspace): add interaction states for the connected teams shor…
ElmanCod110 Oct 19, 2026
3515666
test(workspace): reject decorative fake actions and assert live team …
ElmanCod110 Oct 19, 2026
75f5a38
fix(deploy): apply asset MIME and cache headers to root-hosted assets
ElmanCod110 Oct 19, 2026
ce99494
test(deploy): assert MIME and cache policy for repository-root hosting
ElmanCod110 Oct 19, 2026
fc04e0e
fix(deploy): correct static asset cache matching at repository root
ElmanCod110 Oct 19, 2026
7cba646
test(deploy): validate static-asset cache matcher for both web roots
ElmanCod110 Oct 19, 2026
7b92f9a
ci: cancel superseded checks on the same branch
ElmanCod110 Oct 19, 2026
c694079
test(ci): require branch-scoped cancellation for superseded checks
ElmanCod110 Oct 19, 2026
0b7493d
docs(readme): remove duplicate workspace test command
ElmanCod110 Oct 19, 2026
bd5328b
test(styles): detect undefined CSS custom properties across product s…
ElmanCod110 Oct 19, 2026
28ac2d2
test(ci): match idempotent project file migration DDL
ElmanCod110 Oct 19, 2026
eccc0d1
ci: avoid duplicate push and pull-request runs
ElmanCod110 Oct 19, 2026
1327f36
ci(cd): add manually gated SSH deployment workflow
ElmanCod110 Oct 19, 2026
c5019b8
security(cd): restrict production deploy refs and target paths
ElmanCod110 Oct 19, 2026
824d6e4
docs(cd): document gated deployment and required secrets
ElmanCod110 Oct 19, 2026
54cb35c
test(cd): protect production deployment safety contracts
ElmanCod110 Oct 19, 2026
45bf404
ci(cd): test production deployment safety gates
ElmanCod110 Oct 19, 2026
d4da9f0
docs(release): document guarded manual production delivery
ElmanCod110 Oct 19, 2026
d78ee8c
test(cd): assert deployment never uses destructive rsync flags
ElmanCod110 Oct 19, 2026
fa81259
docs(changelog): record CI failure fixes and guarded CD workflow
ElmanCod110 Oct 19, 2026
22ec82b
ci: update checkout runtime and bound job duration
ElmanCod110 Oct 19, 2026
c035b5b
ci(cd): update checkout runtime for production workflow
ElmanCod110 Oct 19, 2026
5570031
docs(ci): include deployment safety test in local checks
ElmanCod110 Oct 19, 2026
d23077f
test(ci): fix variable interpolation in workspace route assertions
ElmanCod110 Oct 19, 2026
cd7d2b4
fix(workspace): repair literal escapes in hash routing
ElmanCod110 Oct 19, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 16 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -1,8 +1,14 @@
name: PHP Checks

concurrency:
group: php-checks-${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

on:
# Pull requests validate topic branches; push runs are reserved for main to avoid
# duplicate CI runs for every commit on a branch that already has an open PR.
push:
branches: [main, "feat/**", "fix/**", "test/**"]
branches: [main]
pull_request:
workflow_dispatch:

Expand All @@ -12,6 +18,7 @@ permissions:
jobs:
checks:
runs-on: ubuntu-latest
timeout-minutes: 20
services:
mariadb:
image: mariadb:10.11
Expand All @@ -31,7 +38,7 @@ jobs:
php: ["8.2", "8.3", "8.4"]
steps:
- name: Check out repository
uses: actions/checkout@v4
uses: actions/checkout@v5

- name: Set up PHP
uses: shivammathur/setup-php@v2
Expand All @@ -52,10 +59,16 @@ jobs:
- name: Check design-system assets
run: php tests/design-system.php

- name: Check workspace UI contracts
run: php tests/workspace-ui.php

- name: Check deployment workflow safety
run: php tests/deployment-workflow.php

- name: Check JavaScript syntax
run: |
node --check public/assets/js/ui.js
node --check public/assets/js/design-system-demo.js
node --check public/assets/js/workspace.js

- name: Run MariaDB integration tests
env:
Expand Down
106 changes: 106 additions & 0 deletions .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
name: Deploy production

on:
workflow_dispatch:
inputs:
confirm_production:
description: Type DEPLOY to confirm a production deployment
required: true
type: string

permissions:
contents: read

concurrency:
group: production-deployment
cancel-in-progress: false

jobs:
deploy:
if: ${{ inputs.confirm_production == 'DEPLOY' && github.ref == 'refs/heads/main' }}
runs-on: ubuntu-latest
environment: production
timeout-minutes: 15
steps:
- name: Check out the selected revision
uses: actions/checkout@v5

- name: Validate deployment configuration
shell: bash
env:
DEPLOY_HOST: ${{ secrets.DEPLOY_HOST }}
DEPLOY_USER: ${{ secrets.DEPLOY_USER }}
DEPLOY_SSH_PRIVATE_KEY: ${{ secrets.DEPLOY_SSH_PRIVATE_KEY }}
DEPLOY_KNOWN_HOSTS: ${{ secrets.DEPLOY_KNOWN_HOSTS }}
DEPLOY_PATH: ${{ secrets.DEPLOY_PATH }}
run: |
set -euo pipefail
for name in DEPLOY_HOST DEPLOY_USER DEPLOY_SSH_PRIVATE_KEY DEPLOY_KNOWN_HOSTS DEPLOY_PATH; do
if [[ -z "${!name}" ]]; then
echo "::error::Required production secret is missing: ${name}"
exit 1
fi
done
if [[ ! "$DEPLOY_PATH" =~ ^/[A-Za-z0-9._/-]+$ ]]; then
echo "::error::DEPLOY_PATH must be an absolute path using only letters, numbers, dot, underscore, slash, or hyphen"
exit 1
fi
if [[ "$DEPLOY_PATH" == *"/../"* || "$DEPLOY_PATH" == */.. || "$DEPLOY_PATH" == "/" || "$DEPLOY_PATH" == "/home" || "$DEPLOY_PATH" == "/var" ]]; then
echo "::error::DEPLOY_PATH is too broad to be a safe application target"
exit 1
fi

- name: Configure pinned SSH identity
shell: bash
env:
DEPLOY_SSH_PRIVATE_KEY: ${{ secrets.DEPLOY_SSH_PRIVATE_KEY }}
DEPLOY_KNOWN_HOSTS: ${{ secrets.DEPLOY_KNOWN_HOSTS }}
run: |
set -euo pipefail
install -m 700 -d "$HOME/.ssh"
printf '%s\n' "$DEPLOY_SSH_PRIVATE_KEY" > "$HOME/.ssh/deploy_key"
chmod 600 "$HOME/.ssh/deploy_key"
printf '%s\n' "$DEPLOY_KNOWN_HOSTS" > "$HOME/.ssh/known_hosts"
chmod 600 "$HOME/.ssh/known_hosts"

- name: Validate remote SSH access and target directory
shell: bash
env:
DEPLOY_HOST: ${{ secrets.DEPLOY_HOST }}
DEPLOY_USER: ${{ secrets.DEPLOY_USER }}
DEPLOY_PATH: ${{ secrets.DEPLOY_PATH }}
DEPLOY_PORT: ${{ secrets.DEPLOY_PORT }}
run: |
set -euo pipefail
port="${DEPLOY_PORT:-22}"
[[ "$port" =~ ^[0-9]+$ ]] || { echo "::error::DEPLOY_PORT must be numeric"; exit 1; }
ssh -i "$HOME/.ssh/deploy_key" -p "$port" \
-o BatchMode=yes -o StrictHostKeyChecking=yes \
"$DEPLOY_USER@$DEPLOY_HOST" \
"test -d '$DEPLOY_PATH' && test -w '$DEPLOY_PATH'"

- name: Deploy application files without deleting remote data
shell: bash
env:
DEPLOY_HOST: ${{ secrets.DEPLOY_HOST }}
DEPLOY_USER: ${{ secrets.DEPLOY_USER }}
DEPLOY_PATH: ${{ secrets.DEPLOY_PATH }}
DEPLOY_PORT: ${{ secrets.DEPLOY_PORT }}
run: |
set -euo pipefail
port="${DEPLOY_PORT:-22}"
rsync -az --checksum \
--exclude='/.git/' \
--exclude='/.github/' \
--exclude='/tests/' \
--exclude='/docs/' \
--exclude='/.env' \
--exclude='/.env.*' \
--exclude='/*.log' \
-e "ssh -i $HOME/.ssh/deploy_key -p $port -o BatchMode=yes -o StrictHostKeyChecking=yes" \
./ "$DEPLOY_USER@$DEPLOY_HOST:$DEPLOY_PATH/"

- name: Remove temporary SSH credentials
if: always()
shell: bash
run: rm -f "$HOME/.ssh/deploy_key" "$HOME/.ssh/known_hosts"
30 changes: 30 additions & 0 deletions .htaccess
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
Options -Indexes

<IfModule mod_rewrite.c>
RewriteEngine On

# Keep repository metadata and secret dotfiles inaccessible.
RewriteRule (^|/)\.(?!well-known/) - [F,L]

# An optional repository-root document root must never expose the source tree.
# Only the public assets are mapped directly; every application URL goes through PHP.
RewriteRule ^public(?:/|$) - [F,L,NC]
RewriteRule ^\.well-known/ - [END]
RewriteRule ^assets/(.*)$ public/assets/$1 [END,NC]
RewriteRule ^ public/index.php [QSA,END]
</IfModule>

<IfModule mod_mime.c>
AddType text/css .css
AddType application/javascript .js
</IfModule>

<IfModule mod_headers.c>
<FilesMatch "\.(?:css|js)$">
Header set Cache-Control "public, max-age=31536000, immutable"
</FilesMatch>
Header always set X-Content-Type-Options "nosniff"
Header always set Referrer-Policy "strict-origin-when-cross-origin"
Header always set X-Frame-Options "DENY"
Header always set Content-Security-Policy "default-src 'self'; base-uri 'self'; form-action 'self'; frame-ancestors 'none'; object-src 'none'; script-src 'self'; style-src 'self'; img-src 'self' data:; connect-src 'self'"
</IfModule>
11 changes: 11 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,17 @@

## Unreleased

- Add a manual, main-only production delivery workflow with typed confirmation, production environment gate, pinned SSH host verification, destination validation, and non-destructive rsync.
- Add CI contract tests for deployment safety and document required production secrets and controlled migration steps.
- Reduce redundant CI traffic by running topic-branch validation through pull requests and reserving push-triggered CI for main.
- Fix a workspace UI contract test to match idempotent `CREATE TABLE IF NOT EXISTS` migration statements.

- Fix CSS/JavaScript delivery for hosts whose document root is the repository root by routing `/assets/*` to the public asset directory without exposing application source files.
- Explicitly declare static CSS/JavaScript MIME types and apply immutable caching to versioned assets; bump workspace asset keys to avoid stale cached styles and scripts.
- Run the previously unwired workspace UI contract test in CI and syntax-check the production `workspace.js` controller.
- Remove the standalone visual showcase page and demo-only assets; retain shared design-system CSS that the live workspace uses.


- Removed unused duplicate team/project settings handlers and aligned UI contract tests with the registered transactional update endpoints.

- Add a project Files tab with a searchable private source-file directory, lightweight text editor, save/reload controls, and revision-history restore.
Expand Down
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# TeamCode Hub

**Version:** 0.2.0-dev
**Version:** 0.2.2-dev
**Status:** Active development

TeamCode Hub is a modular, security-focused collaboration platform for software teams, designed for conventional PHP hosting with cPanel/DirectAdmin and MySQL/MariaDB.
Expand Down Expand Up @@ -63,16 +63,16 @@ See [docs/API.md](docs/API.md) for request bodies, status codes, retention, and

## Validation

CI checks PHP syntax and smoke tests, design-system and workspace UI contracts, JavaScript syntax, and MariaDB integration tests on PHP 8.2, 8.3, and 8.4.
CI checks PHP syntax and smoke tests, design-system and workspace UI contracts, deployment safety contracts, JavaScript syntax, and MariaDB integration tests on PHP 8.2, 8.3, and 8.4. Production delivery is manual-only from `main`, requires a typed confirmation and pinned SSH host key, and must be configured with environment-scoped deployment secrets; it never deletes remote files or runs migrations automatically.

Run the lightweight local checks with:

```sh
php tests/run.php
php tests/design-system.php
php tests/workspace-ui.php
php tests/deployment-workflow.php
node --check public/assets/js/ui.js
node --check public/assets/js/design-system-demo.js
node --check public/assets/js/workspace.js
```

Expand Down
18 changes: 9 additions & 9 deletions docs/DESIGN-SYSTEM.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,20 +11,20 @@ This document describes the shared visual and interaction foundation used by Tea
| `public/assets/css/base.css` | Browser defaults, semantic element defaults, focus visibility, and reduced motion |
| `public/assets/css/layout.css` | Containers, responsive grids, page shell, sidebar, and header layouts |
| `public/assets/css/components.css` | Buttons, forms, badges, cards, alerts, tables, tabs, overlays, and feedback |
| `public/assets/css/patterns.css` | Upload/range controls, avatar states, pagination, accordions, tooltips, workflow steps, quote/code, and footer patterns |
| `public/assets/css/patterns.css` | Reusable upload/range controls, avatar states, pagination, accordions, tooltips, workflow steps, and content patterns |
| `public/assets/css/utilities.css` | Small composable spacing, text, surface, and alignment helpers |
| `public/assets/js/ui.js` | Accessible interactions shared by product pages |\n| `public/assets/css/workspace.css` | Product-specific workspace, dashboard, project, task, and chat presentation |\n| `public/assets/js/workspace.js` | Authenticated API integration and workspace interactions |\n| `public/app.html` | English workspace shell and sign-in form |
| `public/assets/css/design-system-demo.css` | Styles unique to the reference showcase |
| `public/assets/js/design-system-demo.js` | Demo-only button handlers |
| `public/design-system.html` | English component reference page |
| `public/assets/css/workspace.css` | Product-specific workspace, dashboard, team, project, task, and chat presentation |
| `public/assets/js/ui.js` | Accessible interactions shared by product pages |
| `public/assets/js/workspace.js` | Authenticated API integration and workspace interactions |
| `public/app.html` | English workspace shell and sign-in form |

## Loading the design system

Load the entry stylesheet and the shared UI controller from a page template. Keep product-specific styles and behavior in separate files and avoid inline style or script blocks.

```html
<link rel="stylesheet" href="/assets/css/app.css?v=0.2.1-dev">
<script src="/assets/js/ui.js?v=0.2.0-dev" defer></script>
<link rel="stylesheet" href="/assets/css/app.css?v=0.2.2-dev">
<script src="/assets/js/ui.js?v=0.2.1-dev" defer></script>
```

The entry stylesheet imports tokens, base rules, layout, core components, extended patterns, and utilities in dependency order. Every imported stylesheet URL carries a cache key. When CSS changes, bump the CSS asset query keys in the entry imports and HTML entry points together. A production release should use its actual SemVer version.
Expand Down Expand Up @@ -85,14 +85,14 @@ Run:
```sh
php tests/design-system.php
node --check public/assets/js/ui.js
node --check public/assets/js/design-system-demo.js
node --check public/assets/js/workspace.js
```

CI runs these checks in addition to PHP lint, smoke tests, and MariaDB integration tests. These checks do not replace manual browser review, assistive-technology testing, or visual regression testing.

## Current scope

The showcase is a reference implementation of the style system, not a claim that the product dashboard, team/project screens, or chat interface are complete. Product screens must be built on the existing APIs and retain server-side authentication and authorization checks.
The shared style layers are loaded by the actual workspace shell; they are not a standalone product or a substitute for connected application behavior. Team, project, task, file, and chat actions must use the existing APIs and retain server-side authentication and authorization checks.


## Extended component coverage
Expand Down
14 changes: 14 additions & 0 deletions docs/OPERATIONS.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,27 @@
## Before deployment

- Configure the document root to the repository's `public/` directory where possible.
- If the host cannot point its document root at `public/`, the repository-root `.htaccess` provides a locked-down fallback: it exposes only `/assets/*` from `public/assets/` and routes app paths through `public/index.php`. Do not disable the root rewrite rules, because they prevent direct access to repository source files.
- Configure database environment variables through the host or a private configuration mechanism outside the web root.
- Create a dedicated database user with only the permissions the application requires.
- Enable HTTPS and configure session cookie security accordingly. If TLS terminates at a trusted reverse proxy, set `APP_HTTPS=true` only when the deployment topology guarantees HTTPS to the browser.
- Disable displayed PHP errors in production and enable restricted server-side error logs.
- Confirm private storage, configuration, backup, and log paths cannot be downloaded over HTTP.
- Back up the database and files before applying schema changes.

## GitHub Actions deployment (manual)

The `.github/workflows/deploy.yml` workflow is a manual SSH-based delivery path. It only permits the `main` branch, requires typing `DEPLOY`, uses the GitHub `production` environment, verifies a pinned SSH host key, checks that the target directory already exists and is writable, and does not delete remote files. Configure environment-scoped secrets before using it:

- `DEPLOY_HOST` — server hostname (no protocol or path).
- `DEPLOY_USER` — dedicated deployment account with access only to the application directory.
- `DEPLOY_SSH_PRIVATE_KEY` — private key for that account; never commit it.
- `DEPLOY_KNOWN_HOSTS` — independently verified SSH host-key line(s), not output blindly trusted from a runtime `ssh-keyscan`.
- `DEPLOY_PATH` — absolute application-root path containing the `app/` and `public/` directories; it must not be the filesystem root or a broad system directory.
- `DEPLOY_PORT` — optional SSH port; defaults to `22`. For a non-standard port, the pinned `known_hosts` entry must use the matching `[host]:port` format.

Set production environment reviewers and branch restrictions in GitHub repository settings. Point the web server document root to the deployed `public/` directory when possible. The workflow deliberately does not run database migrations or create an administrator automatically: take a backup, review pending migrations, and apply `php bin/migrate.php` from a trusted server-side CLI session as a separate controlled operation. Configure the required application environment variables on the server before enabling traffic. The first deployment must be tested on a staging target before production use.

## Registration and email verification

Public registration is enabled after the account-token migration has been applied. Configure these environment variables outside the web root:
Expand Down
5 changes: 3 additions & 2 deletions docs/WORKSPACE-UI.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,14 @@ This document maps the English browser application to the existing server APIs.
- `/` — authenticated workspace shell; shows sign-in when there is no active session.
- `/login` — the same progressive-enhancement shell, initially displaying sign-in when unauthenticated.
- `/verify-email` — the workspace shell used to confirm a one-time token supplied in the URL fragment.
- `/design-system.html` — component reference page.
- `public/app.html` — semantic workspace shell.
- `public/assets/css/app.css` and its token/base/layout/component/pattern/utility layers — shared design-system styles.
- `public/assets/css/workspace.css` — product-only styles layered on the core design system.
- `public/assets/js/workspace.js` — same-origin API client, navigation, rendering, forms, and chat polling.
- `public/assets/js/ui.js` — shared design-system interactions.

The preferred deployment sets the document root to `public/`. A repository-root document root is also supported by the top-level `.htaccess`, which maps `/assets/*` to `public/assets/*`, routes application paths to `public/index.php`, and blocks direct access to the `public/` prefix and repository files.

Project repository links accept only public HTTPS GitHub URLs. The server shares one daily cached snapshot per repository and caps refresh attempts to 20 distinct repositories per UTC day.

The HTML has no inline style or script blocks. The front controller returns HTML with a restrictive same-origin Content Security Policy. CSS and JavaScript are external, versioned files; update their query-string version when releasing a changed asset set.
Expand Down Expand Up @@ -103,4 +104,4 @@ php tests/workspace-ui.php
node --check public/assets/js/workspace.js
```

These checks validate the static asset contract and JavaScript syntax. They do not replace manual browser testing across supported devices, keyboard-only interaction, screen-reader review, or visual regression tests. The design-system showcase includes extended input, avatar, chip, accordion, workflow-step, pagination, and content patterns.
These checks validate the static asset contract and JavaScript syntax. They do not replace manual browser testing across supported devices, keyboard-only interaction, screen-reader review, or visual regression tests. The style contract tests check actual production assets and route wiring; reusable patterns stay in the shared `patterns.css` layer instead of a separate demo page.
9 changes: 9 additions & 0 deletions public/.htaccess
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,16 @@ DirectoryIndex index.php
RewriteRule ^ index.php [QSA,L]
</IfModule>

<IfModule mod_mime.c>
# Keep strict nosniff policies compatible with directly served static assets.
AddType text/css .css
AddType application/javascript .js
</IfModule>

<IfModule mod_headers.c>
<FilesMatch "\.(?:css|js)$">
Header set Cache-Control "public, max-age=31536000, immutable"
</FilesMatch>
Header always set X-Content-Type-Options "nosniff"
Header always set Referrer-Policy "strict-origin-when-cross-origin"
Header always set X-Frame-Options "DENY"
Expand Down
Loading
Loading