Compare commits
10 Commits
964588e363
...
codex/webs
| Author | SHA1 | Date | |
|---|---|---|---|
| 3119916ec2 | |||
| c04d3bed5b | |||
| 52646c480c | |||
| 37189a4ac8 | |||
| 6f7a30bc0d | |||
| 9954fbcc0d | |||
| 01edaa0d8a | |||
| b6e0d41691 | |||
| 2981a279ce | |||
| 51211a71c9 |
@@ -1,6 +1,6 @@
|
||||
# Bundle 1 — Checkpoint C review
|
||||
|
||||
Prepared 2026-09-27. **Awaiting owner review.** This covers T06–T07 only; draft/publish permissions, admin editing, public and preview APIs, seed, and frontend adapter have not started.
|
||||
Prepared 2026-09-27. **Approved by the owner on 2026-09-27.** This covers T06–T07 only; draft/publish permissions, admin editing, public and preview APIs, seed, and frontend adapter had not started at this checkpoint.
|
||||
|
||||
## Implemented
|
||||
|
||||
@@ -23,4 +23,4 @@ Prepared 2026-09-27. **Awaiting owner review.** This covers T06–T07 only; draf
|
||||
- `python manage.py check --settings=config.settings.test`: no issues.
|
||||
- `python manage.py makemigrations --check --dry-run --settings=config.settings.test`: no changes detected.
|
||||
|
||||
No production database, media, or public route was changed. This review closes Checkpoint C only if the owner accepts the schema and validation choices above.
|
||||
No production database, media, or public route was changed. The owner approved Checkpoint C and the schema and validation choices above on 2026-09-27.
|
||||
|
||||
28
tasks/bundle1-checkpoint-d-review.md
Normal file
@@ -0,0 +1,28 @@
|
||||
# Bundle 1 — Checkpoint D review
|
||||
|
||||
**Superseded on 2026-09-27; not approved.** The owner rejected this draft/publish workflow and custom admin screens. The replacement [direct-save CMS plan](bundle1-simplification-plan.md) defines the next Checkpoint D review.
|
||||
|
||||
Prepared 2026-09-27. **Awaiting owner review.** This covers T08–T09: editorial draft/publish/restore rules and Django admin editing. Public and private preview APIs (T10–T11), seed (T12–T13), and frontend adapter (T14) have not started.
|
||||
|
||||
## What is ready
|
||||
|
||||
- The `content_editor` group can create and preview drafts. The `content_publisher` group can also publish and restore. Active superusers override these rules. Staff status alone grants no content action. Migration `content.0003_content_groups` creates both groups and their explicit permissions without assigning accounts.
|
||||
- Each save creates a new content revision. Publishing validates the full page and any referenced media, then swaps the published pointer in one transaction. A stale pointer rejects the publish. Restore creates a new revision from a prior published snapshot and keeps history. Audit entries record actor, operation, and revision numbers.
|
||||
- Home publishing requires all four visible sections in the approved order. About requires identity first and contact last. Optional About sections can be reordered or hidden. Listing pages contain an intro and SEO but no sections. Unapproved media cannot be published.
|
||||
- Django admin exposes named fields for site identity, navigation, contacts, page copy, sections, actions, and typed About entries. Editors do not edit raw JSON, revision status, or the published pointer. The dashboard shows drafts, preview links, the published revision, and publisher actions. Approved media becomes read only.
|
||||
- Admin draft preview requires the preview permission, stays on an admin route, and sends `Cache-Control: private, no-store` plus `X-Robots-Tag: noindex, nofollow`. No public content API is active yet.
|
||||
|
||||
## Verification
|
||||
|
||||
- Full backend suite on the isolated SQLite test settings: **79 unit passed; 184 integration passed; 1 PostgreSQL-only concurrency test skipped**.
|
||||
- The two-publisher race test ran separately against a local PostgreSQL test database: **1 passed**. One publish won; the second received a conflict. The SQLite suite also tests a second publish attempt using the same stale pointer.
|
||||
- Django system check: no issues. Migration consistency check: no changes detected. The additive content permission/group migrations applied on the local test database.
|
||||
- Browser walkthrough on a local test database: editor saved a Home draft at 390px, publisher opened its no-store preview and published it, and the dashboard then identified revision 1 as published. The draft form had no horizontal overflow at 390px or 1440px. Captures: [editor form at 390px](evidence/2026-09-27/cms-admin-home-390.png), [editor form at 1440px](evidence/2026-09-27/cms-admin-home-1440.png), [publisher dashboard at 390px](evidence/2026-09-27/cms-admin-publisher-390.png), [published dashboard at 1440px](evidence/2026-09-27/cms-admin-published-1440.png).
|
||||
|
||||
## Review points
|
||||
|
||||
1. Confirm the editor/publisher separation and the admin draft → preview → publish → restore workflow.
|
||||
2. Confirm that the Home section order and About optional reorder/hide controls match the approved contract.
|
||||
3. Confirm the admin layout at phone and desktop widths from the captures. Actual staff account assignment remains a later gate.
|
||||
|
||||
No production content, media, account assignment, or public route was changed. Checkpoint D closes only after owner review; T10–T11 begin afterward.
|
||||
35
tasks/bundle1-development-db-audit.md
Normal file
@@ -0,0 +1,35 @@
|
||||
# Bundle 1 — development database audit
|
||||
|
||||
Audited 2026-09-27 against the configured local PostgreSQL development database, `guilan_ace_local` (`127.0.0.1:5432`). This is the development database the owner authorized in place of staging. No staging or production database was accessed.
|
||||
|
||||
## Migration and data state
|
||||
|
||||
All content migrations are applied: `0001_initial`, `0002_alter_siteidentity_options`, `0003_content_groups`, `0004_direct_edit_content`, and `0005_seed_current_website_copy`.
|
||||
|
||||
Before running the mock-content command, the current editable tables contained one site record, one Home record, one About record, two listing records, and nine About items. There were no image references or missing media files. Django admin change history contained two changes for Home and none for Site, About, Events, or Blog.
|
||||
|
||||
The legacy revision tables were empty: zero SiteIdentity, SiteRevision, EditorialPage, PageRevision, PageSection, MediaAsset, or PublicationAudit rows. Their model definitions, database tables, and historical permission records remain from the applied migration history; no migration or table cleanup was performed.
|
||||
|
||||
## Permission state
|
||||
|
||||
The database has one staff account, which is also the only superuser. The `content_editor` and `content_publisher` groups each have zero members. No user has a direct content-model permission assignment. The two legacy groups contain standard content-model change permissions plus old workflow permission records; they grant access to no account in this database.
|
||||
|
||||
This is an environment-specific snapshot. Staff account assignments in staging or production remain part of the release audit.
|
||||
|
||||
## Mock-content command
|
||||
|
||||
The owner approved mock copy and empty optional images for initial content. The command was first reviewed with:
|
||||
|
||||
```sh
|
||||
python manage.py seed_mock_content --dry-run --database default
|
||||
```
|
||||
|
||||
The dry run proposed replacing Site, About, Events, and Blog, and skipped Home because it has Django admin change history. The command was then run against the same development database:
|
||||
|
||||
```sh
|
||||
python manage.py seed_mock_content --database default
|
||||
```
|
||||
|
||||
It updated those four records, preserved Home, and replaced the untouched About items with six mock items. A read-only follow-up query confirmed the expected mock records, both listing titles and SEO titles, six About items, no image references, and the preserved Home row with its two admin changes. The command did not modify any migration.
|
||||
|
||||
The owner has reviewed and approved the mock content and the development database result. Production initialization remains subject to the release gate.
|
||||
16
tasks/bundle1-seed-review.md
Normal file
@@ -0,0 +1,16 @@
|
||||
# Bundle 1 — mock content initialization
|
||||
|
||||
The owner approved the original source-content review, then directed that the frontend show mock content until staff enter content through Django admin. The existing migrations, including [0005_seed_current_website_copy.py](../../guilan-ace-backend/apps/content/migrations/0005_seed_current_website_copy.py), remain unchanged.
|
||||
|
||||
Use the backend management command [seed_mock_content.py](../../guilan-ace-backend/apps/content/management/commands/seed_mock_content.py) after migrations and before enabling CMS page rendering:
|
||||
|
||||
```sh
|
||||
python manage.py seed_mock_content --dry-run
|
||||
python manage.py seed_mock_content
|
||||
```
|
||||
|
||||
It replaces content for records without a Django admin change-history entry with clearly labeled Persian sample copy, including About items. A record with an admin change entry is preserved as a whole. Re-running the command is safe; `--dry-run` reports intended work without writing. The command is explicit and is not run automatically by deployment or migration.
|
||||
|
||||
Images remain empty because no approved mock image files exist; the frontend treats them as optional. Contact mock URLs use `example.invalid` and the mock email uses that reserved domain.
|
||||
|
||||
The frontend flag remains off by default. The page routes still use legacy rendering until later Bundle 1 tasks wire them to the adapter. Do not enable CMS rendering in a target environment until T08's database audit is complete and the command's dry-run output is reviewed.
|
||||
52
tasks/bundle1-simplification-plan.md
Normal file
@@ -0,0 +1,52 @@
|
||||
# Bundle 1 course correction: simple site content CMS
|
||||
|
||||
Decision recorded 2026-09-27. The owner rejected the draft/publish/revision workflow and custom content admin screens. This plan supersedes the CMS workflow and API portions of the approved Bundle 0 contract and the pending Checkpoint D review. Checkpoint C remains an accurate record of what was reviewed then, but its revision-based schema is no longer the target design. The approved Home/About visual direction and the content ownership inventory remain useful.
|
||||
|
||||
## Agreed behavior
|
||||
|
||||
- The CMS edits the shared site identity and the four fixed public pages: Home, About, Events listing, and Blog listing. Event and blog detail records remain in their existing systems.
|
||||
- A superuser, or an active staff user with the standard Django `change` permission for the relevant content model, edits the record in Django admin. There are no content-specific editor/publisher roles, drafts, publication flags, preview endpoints, revision snapshots, or custom restore action.
|
||||
- Saving a valid record makes its text, images, and SEO available to the public site on the next request. The frontend must not retain a stale page cache after an admin save; verify the actual deployed cache behavior before cutover.
|
||||
- Each fixed page has one editable record with named fields that match the approved layout. Editors do not create pages, edit slugs, manipulate section types/order/visibility, or edit raw JSON. A record's SEO title, description, and social image are editable; canonical routes and structured-data shape stay in code. Shared site identity supplies defaults.
|
||||
- Admin forms use the project's installed Unfold `ModelAdmin`, native fieldsets and supported widgets, matching the existing Blog Category form. No handwritten content admin CSS or custom dashboard/form templates.
|
||||
- Keep useful plain-text, URL, image-signature, size, dimension, and alt-text validation. Use direct image upload fields where practical. Remove media approval and immutability rules that existed only for publication.
|
||||
|
||||
## Work sequence and review gates
|
||||
|
||||
### 1. Migration and permissions audit
|
||||
|
||||
Inspect each target environment for applied `content` migrations, existing content/media rows, and assignments to the content-specific permissions or groups. Record the result before changing schema. Preserve real content and images; map any existing public revision to the new current record. If no published revision exists, do not expose an unreviewed draft automatically. Add forward migrations for shared or applied migration history; do not silently rewrite committed migrations. Translate any actual staff access to standard model permissions before retiring custom permissions. Keep other apps and their permissions untouched.
|
||||
|
||||
**Check:** migration works from a fresh database and a copy with the current content schema; existing real records and access are accounted for.
|
||||
|
||||
### 2. Replace the content model and admin
|
||||
|
||||
Create one fixed record per page and one shared site record, using explicit text, image/alt, and SEO fields for the approved design. Use ordinary Unfold change forms grouped into Content, Images, and SEO. Ensure the four records exist without presenting an arbitrary “Add editorial page” flow; disable deletion. Remove the draft/revision services, publication audit, custom content groups, and custom admin templates after data and permission migration. Django's normal admin history is sufficient for basic change history.
|
||||
|
||||
**Check:** a superuser and a staff user with the relevant `change` permission can save; a staff user without it cannot. Review the actual Home, About, Events, Blog, and site forms at phone and desktop widths against the supplied Blog Category example. Saving invalid text or media shows a field error in the Unfold form.
|
||||
|
||||
### 3. Serve current content
|
||||
|
||||
Expose only the small public read API needed by the frontend for shared site content and the four fixed pages. The implementation and response shapes are documented in the backend [content API v1 note](../../guilan-ace-backend/docs/content-api-v1.md). Responses contain current text, media URLs/alt text, and SEO fields. Remove revision IDs, status, draft preview, publish, and restore from the schema. Define empty-record behavior and cache handling so a successful admin save appears on the next public request.
|
||||
|
||||
**Check:** anonymous reads return current saved values; unknown pages and missing records have explicit responses; an admin save is visible through the API immediately. No draft or preview route exists.
|
||||
|
||||
### 4. Populate reviewed starting content
|
||||
|
||||
Leave all existing migrations unchanged. Use an explicit `seed_mock_content` management command after migrations to ensure the five fixed records contain obvious mock copy until staff save content through Django admin. It replaces untouched migration-provided copy, preserves records with a Django admin change-history entry, supports `--dry-run`, and is never run automatically. Complete the T08 database audit before running it against a target environment; inspect the dry-run output first.
|
||||
|
||||
**Check:** confirm the command fills mock copy, skips admin-edited records, and is idempotent. Optional media remains empty. The owner reviews the mock output before frontend cutover.
|
||||
|
||||
### 5. Connect the frontend and verify SEO
|
||||
|
||||
Keep the frontend feature flag defaulting to legacy content until the new records are complete and checked. Read the fixed page records server-side, populate the approved Home/About layout and Events/Blog listing copy, and use CMS SEO values in rendered HTML and social metadata. Existing event/blog detail APIs and dynamic content stay in place. Remove frontend preview/publish assumptions from later redesign tasks.
|
||||
|
||||
**Check:** editing a heading, image, and SEO title in Unfold changes the public page and server-rendered metadata after Save, with no separate publication action. Verify at 390px and desktop, including Persian text and missing optional images.
|
||||
|
||||
### 6. Release gate
|
||||
|
||||
Rehearse migrations and media on a database copy, check backups, then enable the frontend flag after content and admin walkthrough approval. Keep flag rollback available. Verify public pages, metadata, permissions, and unaffected blog/event flows. Production changes require the existing release gate.
|
||||
|
||||
## Explicit removals from the old plan
|
||||
|
||||
Retire old T08–T11's draft/publish/restore/preview requirements and Checkpoint D's role review. Replace T12–T13's draft seed workflow with reviewed initial records. Update T14, T16–T18, T24, T30, and T38–T39 so their checks require direct-save content, not publication. The old review and contract files remain as historical evidence and are marked superseded, not presented as active instructions.
|
||||
@@ -1,5 +1,7 @@
|
||||
# Public content CMS and API contract — approved for implementation
|
||||
|
||||
**Superseded for CMS implementation on 2026-09-27.** The owner chose [direct-save content editing](bundle1-simplification-plan.md) and rejected the draft/publish/revision and custom admin workflow below. This file records the earlier approval only; use the course correction plan and current [checklist](todo.md) for new work.
|
||||
|
||||
Date: 2026-09-24; revised 2026-09-25; approved by the owner through `bundle0-review.md` on 2026-09-26. Status: **Bundle 0 contract approved for implementation; no public API or schema exists yet**. Inputs: `content-inventory.md`, `product-review.md`, frontend `PRODUCT.md` and `DESIGN.md`, existing Django Ninja routing in `guilan-ace-backend/config/api.py`.
|
||||
|
||||
## Ownership boundary
|
||||
|
||||
38
tasks/concepts/a-campus-wayfinding.html
Normal file
@@ -0,0 +1,38 @@
|
||||
<!doctype html>
|
||||
<html lang="fa" dir="rtl">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link rel="icon" href="./assets/favicon.ico">
|
||||
<title>مسیرهای انجمن — طرح A</title>
|
||||
<style>
|
||||
@font-face{font-family:Vazir;src:url('./assets/Vazirmatn-variable.woff2') format('woff2');font-display:swap}
|
||||
:root{--paper:#f2f6f2;--ink:#103d3b;--green:#226d64;--mint:#c9e2d9;--white:#fff;--signal:#ad3c20;--line:#9cb9af}
|
||||
*{box-sizing:border-box}html{scroll-behavior:smooth}body{margin:0;background:var(--paper);color:var(--ink);font:400 16px/1.8 Vazir,Tahoma,sans-serif}
|
||||
a{color:inherit}a:focus-visible{outline:3px solid var(--signal);outline-offset:4px}::selection{background:var(--mint);color:var(--ink)}
|
||||
.wrap{width:min(100% - 48px,1200px);margin:auto}.mast{border-bottom:1px solid var(--line);background:var(--white)}
|
||||
.mast .wrap{min-height:94px;display:flex;justify-content:space-between;align-items:center;gap:24px}
|
||||
.brand{display:flex;align-items:center;gap:14px;text-decoration:none;min-width:0}.brand img{width:56px;height:56px;object-fit:contain}.brand strong{display:block;font-size:18px;line-height:1.45}.brand small{display:block;color:var(--green);font-size:12px}
|
||||
nav{display:flex;align-items:center;gap:8px;flex-wrap:wrap}nav a{text-decoration:none;font-weight:650;padding:8px 13px;border-radius:4px}nav a:hover{background:var(--mint)}nav .active,nav .active:hover{color:var(--white);background:var(--ink)}
|
||||
.hero{position:relative;overflow:hidden;border-bottom:1px solid var(--line)}.hero:before{content:"";position:absolute;right:max(24px,calc((100% - 1200px)/2));top:0;bottom:0;width:2px;background:var(--signal)}
|
||||
.hero .wrap{display:grid;grid-template-columns:minmax(0,1.25fr) minmax(300px,.75fr);gap:7%;align-items:center;min-height:610px;padding-block:80px}
|
||||
.hero-copy{padding-right:36px;position:relative}.hero-copy:before{content:"";position:absolute;right:-9px;top:31px;width:20px;height:20px;border-radius:50%;background:var(--signal);border:4px solid var(--paper)}
|
||||
h1{font-size:clamp(36px,5.2vw,76px);font-weight:850;line-height:1.25;letter-spacing:-.02em;margin:0 0 24px;max-width:12ch}h1 em{font-style:normal;color:var(--green)}
|
||||
.lead{font-size:clamp(17px,1.6vw,21px);max-width:45ch;margin:0 0 30px}.actions{display:flex;gap:10px;flex-wrap:wrap}.button{display:inline-flex;align-items:center;justify-content:center;min-height:50px;padding:9px 24px;border:2px solid var(--ink);text-decoration:none;font-weight:700;border-radius:4px}.button.primary{background:var(--ink);color:var(--white)}.button:hover{background:var(--signal);border-color:var(--signal);color:var(--white)}
|
||||
.identity-panel{background:var(--ink);color:var(--white);padding:34px 32px;min-height:400px;display:flex;flex-direction:column;justify-content:space-between;position:relative}.identity-panel:after{content:"";position:absolute;left:22px;top:22px;width:20px;height:20px;border:2px solid var(--mint);border-radius:50%}.identity-panel span{color:var(--mint);font-size:13px}.identity-panel p{font-weight:700;font-size:clamp(21px,2.1vw,28px);line-height:1.65;margin:16px 0}.identity-panel .faculty{border-top:1px solid #4c7770;padding-top:18px;font-size:15px}
|
||||
.paths{padding-block:72px}.paths h2,.about h2{font-size:clamp(28px,3vw,42px);line-height:1.4;margin:0 0 26px}.paths .route{display:grid;grid-template-columns:80px 1fr auto;align-items:center;gap:22px;padding:22px 12px;border-top:1px solid var(--line);text-decoration:none}.paths .route:last-child{border-bottom:1px solid var(--line)}.route .mark{display:grid;place-items:center;width:48px;height:48px;background:var(--mint)}.route .mark:before{content:"";width:12px;height:12px;border:3px solid var(--green);border-radius:50%}.route strong{font-size:22px}.route small{display:block;color:#345c55;font-size:14px}.route .arrow{width:14px;height:14px;border-left:2px solid var(--signal);border-bottom:2px solid var(--signal);transform:rotate(45deg)}.route:hover{background:#e3eee8}
|
||||
.about{background:var(--white);border-top:1px solid var(--line)}.about .wrap{display:grid;grid-template-columns:1fr 1fr;gap:8%;padding-block:92px}.about .intro{position:sticky;top:40px;align-self:start}.about .intro p{font-size:20px;max-width:34ch}.about .intro img{width:74px;height:74px;object-fit:contain;margin-top:24px}.about .details{border-right:2px solid var(--signal);padding-right:34px}.detail{padding:4px 0 24px;border-bottom:1px solid var(--line);margin-bottom:25px}.detail h3{font-size:20px;margin:0 0 7px}.detail p{margin:0;color:#345c55}.contact a{display:block;overflow-wrap:anywhere;text-decoration-thickness:1px;text-underline-offset:4px}
|
||||
footer{background:var(--ink);color:var(--white);padding:36px 0}footer .wrap{display:flex;justify-content:space-between;gap:20px;align-items:center}footer p{margin:0}footer a{color:var(--mint)}
|
||||
@media(max-width:760px){.wrap{width:min(100% - 32px,1200px)}.mast .wrap{min-height:76px}.brand img{width:42px;height:42px}.brand strong{font-size:13px}.brand small{font-size:11px}nav{display:none}.hero:before{right:16px}.hero .wrap{grid-template-columns:1fr;gap:34px;min-height:0;padding-block:54px}.hero-copy{padding-right:25px}h1{max-width:14ch}.identity-panel{min-height:260px}.paths{padding-block:50px}.paths .route{grid-template-columns:40px 1fr 18px;gap:12px;padding:17px 2px}.route .mark{width:38px;height:38px;font-size:19px}.route strong{font-size:17px}.route small{font-size:12px}.about .wrap{grid-template-columns:1fr;padding-block:55px;gap:26px}.about .intro{position:static}.about .details{padding-right:20px}footer .wrap{display:block}footer p+ p{margin-top:10px}}
|
||||
@media(prefers-reduced-motion:reduce){html{scroll-behavior:auto}}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<header class="mast"><div class="wrap"><a class="brand" href="#top"><img src="./assets/favicon.ico" alt="نشان فعلی انجمن"><span><strong>انجمن علمی مهندسی کامپیوتر</strong><small>دانشکدهی فنی و مهندسی شرق گیلان</small></span></a><nav aria-label="ناوبری طرح"><a class="active" href="#top">خانه</a><a href="#about">درباره انجمن</a><a href="#paths">مسیرها</a></nav></div></header>
|
||||
<main id="top">
|
||||
<section class="hero" aria-labelledby="hero-title"><div class="wrap"><div class="hero-copy"><h1 id="hero-title">جایی برای <em>یادگیری و همکاری</em> دانشجویان کامپیوتر</h1><p class="lead">انجمن علمی مهندسی کامپیوتر دانشگاه گیلان در دانشکدهی فنی و مهندسی شرق گیلان فعالیت میکند. اینجا میتوانید با فعالیتهای انجمن آشنا شوید و راه مشارکت خود را پیدا کنید.</p><div class="actions"><a class="button primary" href="#about">آشنایی با انجمن</a><a class="button" href="#paths">دیدن مسیرها</a></div></div><div class="identity-panel"><span>هویت انجمن</span><p>انجمن علمی مهندسی کامپیوتر دانشگاه گیلان</p><div class="faculty">دانشکدهی فنی و مهندسی شرق گیلان</div></div></div></section>
|
||||
<section id="paths" class="paths wrap" aria-labelledby="paths-title"><h2 id="paths-title">از کجا شروع کنم؟</h2><a class="route" href="#about"><span class="mark" aria-hidden="true"></span><span><strong>آشنایی با انجمن</strong><small>مأموریت و راههای ارتباطی</small></span><span class="arrow" aria-hidden="true"></span></a><a class="route" href="https://east-guilan-ce.ir/events"><span class="mark" aria-hidden="true"></span><span><strong>رویدادها</strong><small>برنامهها و فرصتهای یادگیری</small></span><span class="arrow" aria-hidden="true"></span></a><a class="route" href="https://east-guilan-ce.ir/blog"><span class="mark" aria-hidden="true"></span><span><strong>بلاگ</strong><small>نوشتهها و محتوای آموزشی</small></span><span class="arrow" aria-hidden="true"></span></a></section>
|
||||
<section id="about" class="about" aria-labelledby="about-title"><div class="wrap"><div class="intro"><h2 id="about-title">درباره انجمن</h2><p>یادگیری پیوسته، اشتراک دانش و فرصت برابر برای مشارکت، محور فعالیت انجمن است.</p><img src="./assets/favicon.ico" alt="نشان فعلی انجمن"></div><div class="details"><div class="detail"><h3>مأموریت</h3><p>توانمندسازی دانشجویان با مهارتهای موردنیاز صنعت، ایجاد شبکههای حرفهای و فراهمکردن مسیرهای یادگیری پروژهمحور.</p></div><div class="detail"><h3>فعالیتها</h3><p>برگزاری رویدادها، کارگاهها و نشستهای تخصصی و انتشار محتوای آموزشی.</p></div><div class="detail contact"><h3>ارتباط با انجمن</h3><a href="mailto:eastguilanceassociation@gmail.com" dir="ltr">eastguilanceassociation@gmail.com</a><a href="https://t.me/guilancea" dir="ltr">t.me/guilancea</a><a href="https://instagram.com/guilance.ir" dir="ltr">instagram.com/guilance.ir</a></div></div></div></section>
|
||||
</main><footer><div class="wrap"><p>انجمن علمی مهندسی کامپیوتر دانشگاه گیلان</p><p><a href="#top">بازگشت به آغاز</a></p></div></footer>
|
||||
</body>
|
||||
</html>
|
||||
32
tasks/concepts/b-association-journal.html
Normal file
@@ -0,0 +1,32 @@
|
||||
<!doctype html>
|
||||
<html lang="fa" dir="rtl">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link rel="icon" href="./assets/favicon.ico">
|
||||
<title>دفتر انجمن — طرح B</title>
|
||||
<style>
|
||||
@font-face{font-family:Vazir;src:url('./assets/Vazirmatn-variable.woff2') format('woff2');font-display:swap}
|
||||
:root{--navy:#14243f;--blue:#42649a;--paper:#fff9ea;--cream:#f1e8d0;--gold:#f0be54;--white:#fff;--line:#b8b2a4}
|
||||
*{box-sizing:border-box}html{scroll-behavior:smooth}body{margin:0;background:var(--paper);color:var(--navy);font:400 16px/1.85 Vazir,Tahoma,sans-serif}a{color:inherit}a:focus-visible{outline:3px solid var(--gold);outline-offset:4px}::selection{background:var(--gold);color:var(--navy)}
|
||||
.wrap{width:min(100% - 56px,1240px);margin:auto}.header{background:var(--navy);color:var(--white);border-bottom:1px solid #66758e}.header .wrap{display:flex;justify-content:space-between;align-items:center;gap:30px;min-height:92px}.brand{display:flex;align-items:center;gap:13px;text-decoration:none}.brand img{height:52px;width:52px;object-fit:contain}.brand span{font-size:14px;font-weight:750;max-width:250px;line-height:1.5}.header nav{display:flex;gap:26px}.header nav a{color:#ecf0f7;text-decoration:none;text-underline-offset:9px;font-size:14px}.header nav a:hover,.header nav a:focus-visible{text-decoration:underline}
|
||||
.cover{background:var(--navy);color:var(--paper);overflow:hidden}.cover .wrap{padding-block:70px 90px;display:grid;grid-template-columns:1.3fr .7fr;gap:8%;min-height:620px;align-items:end}.headline{position:relative}h1{font-weight:900;font-size:clamp(48px,7.3vw,112px);letter-spacing:-.035em;line-height:1.16;margin:0;max-width:12ch}h1 span{display:block;color:var(--gold)}.headline:after{content:"";display:block;width:70%;height:1px;background:#8190a3;margin-top:36px}.cover-note{align-self:center;border-top:2px solid var(--gold);background:var(--paper);color:var(--navy);padding:28px 30px 32px}.cover-note p{font-size:19px;line-height:1.8;margin:0 0 24px}.cover-note a{display:inline-block;font-weight:750;text-underline-offset:5px}.cover-note strong{display:block;font-size:13px;color:#49628b;margin-bottom:18px}
|
||||
.ribbon{background:var(--gold);padding:16px 0}.ribbon .wrap{display:flex;align-items:center;gap:32px;flex-wrap:wrap;font-weight:750}.ribbon span{margin-left:auto}.ribbon a{text-decoration:none;border-bottom:1px solid currentColor}.ribbon a:hover{text-decoration:underline}
|
||||
.chapter{padding-block:88px 100px}.chapter-head{display:grid;grid-template-columns:1fr 1fr;gap:7%;padding-bottom:40px;border-bottom:2px solid var(--navy)}.chapter h2{font-size:clamp(36px,4.2vw,62px);line-height:1.25;margin:0}.chapter-head p{font-size:21px;line-height:1.8;margin:0}.ledger{display:grid;grid-template-columns:repeat(3,1fr);border-bottom:1px solid var(--line)}.ledger div{padding:30px 22px 32px;border-left:1px solid var(--line)}.ledger div:first-child{padding-right:0}.ledger div:last-child{border:0}.ledger h3{font-size:23px;margin:0 0 10px}.ledger p{margin:0;color:#384c69}
|
||||
.about{background:var(--cream);padding-block:92px}.about .wrap{display:grid;grid-template-columns:.8fr 1.2fr;gap:9%}.about aside{border-top:2px solid var(--navy);padding-top:22px}.about aside img{width:86px;height:86px;object-fit:contain}.about h2{font-size:clamp(38px,5vw,70px);line-height:1.2;margin:15px 0 26px}.about .name{font-weight:760;font-size:20px;line-height:1.7}.about article{padding-top:34px}.about article p{font-size:20px;line-height:2;max-width:44ch;margin:0 0 32px}.about dl{border-top:1px solid var(--navy);margin:0}.about dl div{display:grid;grid-template-columns:130px 1fr;border-bottom:1px solid var(--line);gap:20px;padding:18px 0}.about dt{font-weight:800}.about dd{margin:0;overflow-wrap:anywhere}.about dd a{text-underline-offset:4px}
|
||||
.end{background:var(--navy);color:var(--paper);padding:58px 0}.end .wrap{display:flex;justify-content:space-between;align-items:end;gap:30px}.end p{margin:0;font-weight:700;font-size:22px;max-width:30ch}.end a{color:var(--gold);white-space:nowrap}
|
||||
@media(max-width:820px){.wrap{width:min(100% - 32px,1240px)}.header .wrap{min-height:75px}.brand img{width:42px;height:42px}.brand span{font-size:12px}.header nav{display:none}.cover .wrap{grid-template-columns:1fr;gap:52px;min-height:0;padding-block:52px 68px}.cover-note{max-width:550px}.ribbon .wrap{gap:12px}.ribbon span{width:100%}.chapter{padding-block:58px}.chapter-head{grid-template-columns:1fr;gap:17px}.chapter-head p{font-size:18px}.ledger{grid-template-columns:1fr}.ledger div,.ledger div:first-child{padding:23px 0;border-left:0;border-bottom:1px solid var(--line)}.ledger div:last-child{border:0}.about{padding-block:60px}.about .wrap{grid-template-columns:1fr;gap:20px}.about article{padding-top:0}.about article p{font-size:18px}.end .wrap{display:block}.end a{display:inline-block;margin-top:22px}}
|
||||
@media(max-width:400px){h1{font-size:45px}.about dl div{grid-template-columns:1fr;gap:3px}.cover-note{padding:22px}}
|
||||
@media(prefers-reduced-motion:reduce){html{scroll-behavior:auto}}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<header class="header"><div class="wrap"><a class="brand" href="#top"><img src="./assets/favicon.ico" alt="نشان فعلی انجمن"><span>انجمن علمی مهندسی کامپیوتر<br>دانشکدهی فنی و مهندسی شرق گیلان</span></a><nav aria-label="ناوبری طرح"><a href="#top">آغاز</a><a href="#chapter">فعالیتها</a><a href="#about">درباره انجمن</a></nav></div></header>
|
||||
<main id="top">
|
||||
<section class="cover" aria-labelledby="cover-title"><div class="wrap"><div class="headline"><h1 id="cover-title">انجمن برای <span>یاد گرفتن با هم</span></h1></div><div class="cover-note"><strong>انجمن علمی مهندسی کامپیوتر دانشگاه گیلان</strong><p>در دانشکدهی فنی و مهندسی شرق گیلان، فعالیت انجمن فرصتی برای یادگیری، اشتراک دانش و مشارکت دانشجویان است.</p><a href="#about">انجمن را بشناسید</a></div></div></section>
|
||||
<div class="ribbon"><div class="wrap"><span>برای آشنایی با فعالیتها</span><a href="#chapter">برنامههای انجمن</a><a href="#about">مأموریت و ارتباط</a></div></div>
|
||||
<section id="chapter" class="chapter wrap" aria-labelledby="chapter-title"><div class="chapter-head"><h2 id="chapter-title">فعالیتهای انجمن</h2><p>از رویدادهای تخصصی تا نوشتههای آموزشی، برنامهها برای یادگیری و ارتباط میان دانشجویان شکل میگیرند.</p></div><div class="ledger"><div><h3>رویدادها</h3><p>کارگاهها، سمینارها و نشستهای علمی.</p></div><div><h3>محتوا</h3><p>مقالات آموزشی و یادداشتهای تخصصی.</p></div><div><h3>همکاری</h3><p>مشارکت دانشجویان و اشتراک تجربه.</p></div></div></section>
|
||||
<section id="about" class="about" aria-labelledby="about-title"><div class="wrap"><aside><img src="./assets/favicon.ico" alt="نشان فعلی انجمن"><h2 id="about-title">درباره انجمن</h2><p class="name">انجمن علمی مهندسی کامپیوتر دانشگاه گیلان (دانشکدهی فنی و مهندسی شرق گیلان)</p></aside><article><p>مأموریت انجمن، توانمندسازی دانشجویان با مهارتهای موردنیاز صنعت، ایجاد شبکههای حرفهای و فراهمکردن مسیرهای یادگیری پروژهمحور است.</p><dl><div><dt>ایمیل</dt><dd><a dir="ltr" href="mailto:eastguilanceassociation@gmail.com">eastguilanceassociation@gmail.com</a></dd></div><div><dt>تلگرام</dt><dd><a dir="ltr" href="https://t.me/guilancea">t.me/guilancea</a></dd></div><div><dt>اینستاگرام</dt><dd><a dir="ltr" href="https://instagram.com/guilance.ir">instagram.com/guilance.ir</a></dd></div></dl></article></div></section>
|
||||
</main><footer class="end"><div class="wrap"><p>یادگیری پیوسته و اشتراک دانش، از ارزشهای انجمن است.</p><a href="#top">بازگشت به آغاز</a></div></footer>
|
||||
</body>
|
||||
</html>
|
||||
52
tasks/concepts/c-current-refinement.html
Normal file
@@ -0,0 +1,52 @@
|
||||
<!doctype html>
|
||||
<html lang="fa" dir="rtl">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link rel="icon" href="./assets/favicon.ico">
|
||||
<title>پالایش طرح فعلی — خانه و درباره انجمن</title>
|
||||
<style>
|
||||
@font-face{font-family:Vazirmatn;src:url('./assets/Vazirmatn-variable.woff2') format('woff2');font-display:swap}
|
||||
:root{--background:#fff;--ink:#0f172a;--muted:#475569;--line:#e2e8f0;--wash:#f4f8fc;--accent:#1e4f85;--focus:#2563eb}
|
||||
*{box-sizing:border-box}html{scroll-behavior:smooth}body{margin:0;background:var(--background);color:var(--ink);font:400 16px/1.8 Vazirmatn,Tahoma,sans-serif}a{color:inherit}a:focus-visible,summary:focus-visible{outline:3px solid var(--focus);outline-offset:4px}::selection{background:#dbeafe;color:var(--ink)}
|
||||
.wrap{width:min(100% - 48px,1120px);margin-inline:auto}.site-head{position:sticky;top:0;z-index:10;background:#ffffffed;border-bottom:1px solid var(--line);backdrop-filter:blur(16px)}
|
||||
.site-head .wrap{min-height:74px;display:flex;align-items:center;justify-content:space-between;gap:24px}.brand{display:flex;align-items:center;gap:10px;min-width:0;text-decoration:none}.brand img{width:39px;height:39px;object-fit:contain}.brand strong{display:block;font-size:15px;line-height:1.5}.brand small{display:block;color:var(--muted);font-size:11px;line-height:1.5}
|
||||
.top-nav{display:flex;align-items:center;gap:5px}.top-nav a{padding:9px 15px;border-radius:999px;text-decoration:none;font-size:14px;font-weight:600;color:var(--muted)}.top-nav a:hover,.top-nav a.active{background:var(--wash);color:var(--ink)}
|
||||
.hero{background:linear-gradient(180deg,#f3f8ff 0%,#fff 100%);text-align:center;padding:clamp(78px,9vw,126px) 0 100px}.hero h1{max-width:19ch;margin:0 auto 23px;font-size:clamp(36px,4.4vw,61px);line-height:1.33;font-weight:850;letter-spacing:-.025em}.hero .lead{max-width:61ch;margin:0 auto;color:var(--muted);font-size:clamp(17px,1.6vw,20px);line-height:1.95}.hero .actions{margin-top:34px}
|
||||
.actions{display:flex;flex-wrap:wrap;justify-content:center;gap:12px}.button{display:inline-flex;align-items:center;justify-content:center;min-height:48px;padding:10px 24px;border-radius:10px;border:1px solid var(--ink);text-decoration:none;font-weight:700;line-height:1.5;transition:background .18s,color .18s,border-color .18s}.button.primary{background:var(--ink);color:white}.button.secondary{background:white;color:var(--ink);border-color:#cbd5e1}.button:hover{background:var(--accent);border-color:var(--accent);color:white}
|
||||
.section{padding:86px 0}.section-head{display:flex;justify-content:space-between;align-items:end;gap:24px;margin-bottom:26px}.section h2{font-size:clamp(26px,3vw,35px);line-height:1.45;margin:0}.section-head p{max-width:47ch;margin:0;color:var(--muted)}
|
||||
.offer-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:14px}.offer{padding:27px;border:1px solid var(--line);border-radius:16px;background:white}.offer h3{font-size:20px;margin:0 0 10px}.offer p{color:var(--muted);font-size:15px;line-height:1.9;margin:0 0 20px}.text-link{color:var(--accent);text-decoration:underline;text-underline-offset:5px;font-weight:700}.text-link:hover{color:var(--ink)}
|
||||
.home-join{background:var(--wash);border-block:1px solid var(--line);text-align:center}.home-join p{color:var(--muted);max-width:50ch;margin:12px auto 24px}.about-hero{padding:86px 0 70px;background:linear-gradient(180deg,#f4f8fc,white)}.about-hero h1{font-size:clamp(35px,4.3vw,55px);line-height:1.35;margin:0 0 16px}.about-hero p{max-width:68ch;margin:0;color:var(--muted);font-size:18px;line-height:1.95}.about-full-name{margin-top:30px;padding-top:18px;border-top:1px solid var(--line);font-size:14px;color:var(--muted)}
|
||||
.about-layout{display:grid;grid-template-columns:minmax(0,1.2fr) minmax(0,.8fr);gap:clamp(32px,6vw,90px)}.about-layout h2{margin:0 0 16px}.about-layout p{max-width:66ch;color:var(--muted);margin:0 0 22px}.activity-list{list-style:none;padding:0;margin:0}.activity-list li{padding:15px 0;border-bottom:1px solid var(--line)}.activity-list strong{display:block;font-size:17px}.activity-list span{display:block;color:var(--muted);font-size:14px}.contact-block{border:1px solid var(--line);border-radius:16px;padding:25px;background:var(--wash);align-self:start}.contact-block h3{font-size:20px;margin:0 0 10px}.contact-block p{font-size:14px;margin:0 0 18px}.contact-block a{display:block;overflow-wrap:anywhere;margin:8px 0;color:var(--accent);text-underline-offset:4px}.contact-block a[dir=ltr]{text-align:right}
|
||||
.faq{padding-top:0}.faq details{border-bottom:1px solid var(--line);padding:17px 0}.faq summary{cursor:pointer;font-weight:650}.faq details p{color:var(--muted);margin:12px 0 0;max-width:65ch}.site-foot{border-top:1px solid var(--line);padding:35px 0;color:var(--muted);font-size:14px}.site-foot .wrap{display:flex;justify-content:space-between;gap:16px}.mobile-nav{display:none}
|
||||
@media(max-width:700px){.wrap{width:min(100% - 32px,1120px)}.site-head .wrap{min-height:68px}.brand img{width:33px;height:33px}.brand small{display:none}.top-nav{display:none}.hero{padding:82px 0 72px}.hero h1{font-size:clamp(35px,9vw,48px)}.hero .lead{font-size:17px}.actions{flex-direction:column}.button{width:100%}.section{padding:62px 0}.section-head{display:block}.section-head p{margin-top:8px}.offer-grid,.about-layout{grid-template-columns:1fr}.offer{padding:23px}.about-hero{padding:70px 0 52px}.about-hero p{font-size:17px}.contact-block{margin-top:10px}.faq{padding-top:0}.site-foot{padding-bottom:130px}.site-foot .wrap{display:block}.site-foot p{margin:0 0 8px}.mobile-nav{position:fixed;z-index:20;bottom:calc(12px + env(safe-area-inset-bottom));left:16px;right:16px;display:flex;justify-content:space-around;gap:4px;padding:8px;border-radius:20px;background:#fffffff0;border:1px solid var(--line);box-shadow:0 12px 32px #0f172a20;backdrop-filter:blur(14px)}.mobile-nav a{flex:1;text-align:center;text-decoration:none;padding:7px 2px;border-radius:14px;font-size:12px;font-weight:700}.mobile-nav a.active{background:var(--ink);color:white}}
|
||||
#about,#home,#contact{scroll-margin-top:80px}
|
||||
@media(prefers-reduced-motion:reduce){html{scroll-behavior:auto}.button{transition:none}}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<header class="site-head"><div class="wrap"><a class="brand" href="#home"><img src="./assets/favicon.ico" alt=""><span><strong>انجمن علمی مهندسی کامپیوتر</strong><small>دانشکدهی فنی و مهندسی شرق گیلان</small></span></a><nav class="top-nav" aria-label="بخشهای نمونه"><a class="active" href="#home">خانه</a><a href="#about">درباره انجمن</a><a href="https://east-guilan-ce.ir/events">رویدادها</a><a href="https://east-guilan-ce.ir/blog">بلاگ</a></nav></div></header>
|
||||
<main>
|
||||
<section class="hero" id="home" aria-labelledby="home-title"><div class="wrap"><h1 id="home-title">انجمن علمی مهندسی کامپیوتر</h1><p class="lead">انجمن دانشجویان کامپیوتر در دانشکدهی فنی و مهندسی شرق گیلان است؛ جایی برای یادگیری، برگزاری رویدادهای تخصصی و همکاری با یکدیگر.</p><div class="actions"><a class="button primary" href="#about">آشنایی با انجمن</a><a class="button secondary" href="https://east-guilan-ce.ir/events">مشاهده رویدادها</a></div></div></section>
|
||||
<section class="section" aria-labelledby="offer-title"><div class="wrap"><div class="section-head"><h2 id="offer-title">در انجمن چه میگذرد؟</h2><p>سه راه ساده برای شناختن فعالیتهای انجمن و پیدا کردن نقطهی شروع.</p></div><div class="offer-grid"><article class="offer"><h3>رویدادهای تخصصی</h3><p>کارگاهها، سمینارها و نشستهایی برای یادگیری مهارتهای تازه.</p><a class="text-link" href="https://east-guilan-ce.ir/events">دیدن رویدادها</a></article><article class="offer"><h3>محتوای آموزشی</h3><p>مطالبی در حوزههای گوناگون علوم و مهندسی کامپیوتر.</p><a class="text-link" href="https://east-guilan-ce.ir/blog">خواندن بلاگ</a></article><article class="offer"><h3>همکاری دانشجویی</h3><p>فرصتی برای مشارکت در فعالیتها و ارتباط با دانشجویان علاقهمند.</p><a class="text-link" href="#contact">راههای ارتباطی</a></article></div></div></section>
|
||||
<section class="section home-join" aria-labelledby="join-title"><div class="wrap"><h2 id="join-title">دوست دارید همراه انجمن باشید؟</h2><p>برای آشنایی با فعالیتها از همینجا شروع کنید یا برنامههای پیش رو را ببینید.</p><div class="actions"><a class="button primary" href="#about">درباره انجمن</a><a class="button secondary" href="https://east-guilan-ce.ir/events">رویدادها</a></div></div></section>
|
||||
<section class="about-hero" id="about" aria-labelledby="about-title"><div class="wrap"><h1 id="about-title">درباره انجمن</h1><p>انجمن علمی مهندسی کامپیوتر در دانشکدهی فنی و مهندسی شرق گیلان با برگزاری رویدادها و تولید محتوای آموزشی، فضایی برای یادگیری و همکاری دانشجویان فراهم میکند.</p><div class="about-full-name">انجمن علمی مهندسی کامپیوتر دانشگاه گیلان (دانشکدهی فنی و مهندسی شرق گیلان)</div></div></section>
|
||||
<section class="section" aria-labelledby="activity-title"><div class="wrap about-layout"><div><h2 id="activity-title">فعالیتهای انجمن</h2><p>محور فعالیتها، یادگیری، اشتراک دانش و ارتباط میان دانشجویان و متخصصان است.</p><ul class="activity-list"><li><strong>برگزاری رویدادها</strong><span>کارگاهها، سمینارها و نشستهای تخصصی</span></li><li><strong>انتشار محتوا</strong><span>مقالهها و مطالب آموزشی در حوزهی کامپیوتر</span></li><li><strong>مشارکت دانشجویی</strong><span>همکاری در برنامهریزی و اجرای فعالیتهای انجمن</span></li></ul></div><aside class="contact-block" id="contact"><h3>ارتباط با انجمن</h3><p>برای پیشنهاد برنامه، همکاری یا پرسش، از یکی از راههای زیر پیام بدهید.</p><a dir="ltr" href="mailto:eastguilanceassociation@gmail.com">eastguilanceassociation@gmail.com</a><a dir="ltr" href="https://t.me/guilancea">t.me/guilancea</a><a dir="ltr" href="https://instagram.com/guilance.ir">instagram.com/guilance.ir</a></aside></div></section>
|
||||
<section class="section faq" aria-labelledby="faq-title"><div class="wrap"><h2 id="faq-title">پرسشهای رایج</h2><details><summary>چطور در رویدادها شرکت کنم؟</summary><p>رویداد موردنظرتان را انتخاب کنید و شرایط و مراحل ثبتنام همان رویداد را بخوانید.</p></details><details><summary>چطور با انجمن همکاری کنم؟</summary><p>برای پیشنهاد همکاری از راههای ارتباطی بالا پیام بدهید.</p></details></div></section>
|
||||
</main>
|
||||
<footer class="site-foot"><div class="wrap"><p>انجمن علمی مهندسی کامپیوتر دانشگاه گیلان</p><a href="#home">بازگشت به آغاز</a></div></footer>
|
||||
<nav class="mobile-nav" aria-label="ناوبری نمونه در موبایل"><a class="active" href="#home">خانه</a><a href="#about">درباره</a><a href="https://east-guilan-ce.ir/events">رویدادها</a><a href="https://east-guilan-ce.ir/blog">بلاگ</a></nav>
|
||||
<script>
|
||||
function syncPreviewNav(){
|
||||
const current = location.hash === '#about' ? '#about' : '#home';
|
||||
for(const link of document.querySelectorAll('.top-nav a[href^="#"],.mobile-nav a[href^="#"]')){
|
||||
const active = link.getAttribute('href') === current;
|
||||
link.classList.toggle('active',active);
|
||||
if(active) link.setAttribute('aria-current','page'); else link.removeAttribute('aria-current');
|
||||
}
|
||||
}
|
||||
addEventListener('hashchange',syncPreviewNav);
|
||||
syncPreviewNav();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
BIN
tasks/evidence/2026-09-24/concept-a-1440.png
Normal file
|
After Width: | Height: | Size: 197 KiB |
BIN
tasks/evidence/2026-09-24/concept-a-390.png
Normal file
|
After Width: | Height: | Size: 141 KiB |
BIN
tasks/evidence/2026-09-24/concept-b-1440.png
Normal file
|
After Width: | Height: | Size: 193 KiB |
BIN
tasks/evidence/2026-09-24/concept-b-390.png
Normal file
|
After Width: | Height: | Size: 150 KiB |
74
tasks/evidence/2026-09-24/create-local-baseline-fixtures.py
Normal file
@@ -0,0 +1,74 @@
|
||||
"""Create disposable Bundle 0 browser fixtures in the isolated Django test database.
|
||||
|
||||
Run only with --settings=config.settings.test and BUNDLE0_TEST_PASSWORD set.
|
||||
Never point TEST_DB_NAME at a shared or production database.
|
||||
"""
|
||||
|
||||
import os
|
||||
from datetime import timedelta
|
||||
|
||||
from django.conf import settings
|
||||
from django.utils import timezone
|
||||
|
||||
from apps.blog.models import Post
|
||||
from apps.events.models import Event
|
||||
from apps.users.models import User
|
||||
|
||||
|
||||
assert os.environ.get("DJANGO_SETTINGS_MODULE") == "config.settings.test"
|
||||
assert settings.DATABASES["default"]["ENGINE"] == "django.db.backends.sqlite3"
|
||||
password = os.environ["BUNDLE0_TEST_PASSWORD"]
|
||||
now = timezone.now()
|
||||
|
||||
|
||||
def fixture_user(username, mobile, *, staff=False):
|
||||
user, _ = User.objects.get_or_create(
|
||||
username=username,
|
||||
defaults={"mobile": mobile, "email": f"{username}@example.invalid"},
|
||||
)
|
||||
user.mobile = mobile
|
||||
user.email = f"{username}@example.invalid"
|
||||
user.is_email_verified = True
|
||||
user.is_mobile_verified = True
|
||||
user.is_staff = staff
|
||||
user.is_superuser = staff
|
||||
user.set_password(password)
|
||||
user.save()
|
||||
return user
|
||||
|
||||
|
||||
student = fixture_user("bundle0_student", "09198000111")
|
||||
staff = fixture_user("bundle0_staff", "09198000112", staff=True)
|
||||
|
||||
for slug, title, price, capacity in (
|
||||
("bundle0-free", "Bundle 0 Free Event", 0, 20),
|
||||
("bundle0-full", "Bundle 0 Full Event", 0, 0),
|
||||
("bundle0-paid", "Bundle 0 Paid Event", 50000, 20),
|
||||
):
|
||||
Event.objects.update_or_create(
|
||||
slug=slug,
|
||||
defaults={
|
||||
"title": title,
|
||||
"description": "Local baseline event for browser review.",
|
||||
"start_time": now + timedelta(days=7),
|
||||
"end_time": now + timedelta(days=7, hours=2),
|
||||
"registration_start_date": now - timedelta(days=1),
|
||||
"registration_end_date": now + timedelta(days=6),
|
||||
"status": Event.StatusChoices.PUBLISHED,
|
||||
"price": price,
|
||||
"capacity": capacity,
|
||||
},
|
||||
)
|
||||
|
||||
Post.objects.update_or_create(
|
||||
slug="bundle0-article",
|
||||
defaults={
|
||||
"title": "Bundle 0 Article",
|
||||
"content": "# Local baseline article\n\nA sample article for the browser review.",
|
||||
"author": staff,
|
||||
"status": Post.StatusChoices.PUBLISHED,
|
||||
"published_at": now,
|
||||
},
|
||||
)
|
||||
|
||||
print("Created isolated student, staff, free/full/paid events, and article fixtures.")
|
||||
95
tasks/evidence/2026-09-24/replay-local-auth.py
Normal file
@@ -0,0 +1,95 @@
|
||||
"""Replay signup, OTP login, and password reset in the isolated test database.
|
||||
|
||||
SMS and the short-lived OTP store are mocked. Run after the fixture seed with
|
||||
--settings=config.settings.test and BUNDLE0_TEST_PASSWORD set.
|
||||
"""
|
||||
|
||||
import json
|
||||
import os
|
||||
from unittest.mock import patch
|
||||
|
||||
from django.conf import settings
|
||||
from django.test import Client, override_settings
|
||||
|
||||
from apps.users.models import User
|
||||
|
||||
|
||||
assert os.environ.get("DJANGO_SETTINGS_MODULE") == "config.settings.test"
|
||||
assert settings.DATABASES["default"]["ENGINE"] == "django.db.backends.sqlite3"
|
||||
password = os.environ["BUNDLE0_TEST_PASSWORD"]
|
||||
mobile = "09198000113"
|
||||
client = Client()
|
||||
|
||||
|
||||
class TemporaryOtpStore:
|
||||
def __init__(self):
|
||||
self.values = {}
|
||||
|
||||
def setex(self, key, _seconds, value):
|
||||
self.values[key] = str(value).encode("utf-8")
|
||||
|
||||
def get(self, key):
|
||||
return self.values.get(key)
|
||||
|
||||
def delete(self, key):
|
||||
self.values.pop(key, None)
|
||||
|
||||
|
||||
store = TemporaryOtpStore()
|
||||
|
||||
|
||||
def post(path, payload):
|
||||
return client.post(path, data=json.dumps(payload), content_type="application/json")
|
||||
|
||||
|
||||
with (
|
||||
override_settings(ALLOWED_HOSTS=["testserver", "localhost"]),
|
||||
patch("apps.users.services.auth._redis", return_value=store),
|
||||
patch("apps.users.services.auth.send_critical_sms.delay"),
|
||||
):
|
||||
invalid = post("/api/auth/otp/send", {"mobile": "123", "mode": "register"})
|
||||
assert invalid.status_code == 400
|
||||
|
||||
sent = post("/api/auth/otp/send", {"mobile": mobile, "mode": "register"})
|
||||
assert sent.status_code == 200, sent.content
|
||||
code = store.get(f"auth_otp:register:{mobile}").decode("utf-8")
|
||||
wrong = "00000" if code != "00000" else "99999"
|
||||
rejected = post("/api/auth/otp/verify-register", {"mobile": mobile, "code": wrong})
|
||||
assert rejected.status_code == 400
|
||||
verified = post("/api/auth/otp/verify-register", {"mobile": mobile, "code": code})
|
||||
assert verified.status_code == 200, verified.content
|
||||
registered = post(
|
||||
"/api/auth/register",
|
||||
{"username": "bundle0_signup", "mobile": mobile, "code": code, "password": password},
|
||||
)
|
||||
assert registered.status_code == 201, registered.content
|
||||
assert User.objects.get(username="bundle0_signup").is_mobile_verified
|
||||
|
||||
duplicate = post("/api/auth/otp/send", {"mobile": mobile, "mode": "register"})
|
||||
assert duplicate.status_code == 400
|
||||
|
||||
sent = post("/api/auth/otp/send", {"mobile": mobile, "mode": "login"})
|
||||
assert sent.status_code == 200, sent.content
|
||||
code = store.get(f"auth_otp:login:{mobile}").decode("utf-8")
|
||||
login = post("/api/auth/login/otp", {"mobile": mobile, "code": code})
|
||||
assert login.status_code == 200 and login.json()["access_token"]
|
||||
|
||||
sent = post("/api/auth/otp/send", {"mobile": mobile, "mode": "reset_password"})
|
||||
assert sent.status_code == 200, sent.content
|
||||
code = store.get(f"auth_otp:reset_password:{mobile}").decode("utf-8")
|
||||
new_password = password + "New"
|
||||
rejected = post(
|
||||
"/api/auth/reset-password",
|
||||
{"mobile": mobile, "code": wrong if wrong != code else "88888", "new_password": new_password},
|
||||
)
|
||||
assert rejected.status_code == 400
|
||||
changed = post(
|
||||
"/api/auth/reset-password",
|
||||
{"mobile": mobile, "code": code, "new_password": new_password},
|
||||
)
|
||||
assert changed.status_code == 200, changed.content
|
||||
old_login = post("/api/auth/login", {"identifier": mobile, "password": password})
|
||||
new_login = post("/api/auth/login", {"identifier": mobile, "password": new_password})
|
||||
assert old_login.status_code == 401 and new_login.status_code == 200
|
||||
|
||||
print("Auth replay: invalid OTP request and code rejected; signup, OTP login, and password reset succeeded; old password rejected.")
|
||||
85
tasks/evidence/2026-09-24/replay-local-payment.py
Normal file
@@ -0,0 +1,85 @@
|
||||
"""Replay paid-event outcomes in the isolated Bundle 0 Django test database.
|
||||
|
||||
External gateway calls, notifications, and SMS are mocked. This script is baseline
|
||||
evidence, not a production payment test or a browser gateway substitute.
|
||||
"""
|
||||
|
||||
import json
|
||||
import os
|
||||
from unittest.mock import Mock, patch
|
||||
|
||||
from django.conf import settings
|
||||
from django.test import Client, override_settings
|
||||
|
||||
from apps.events.models import Event, Registration
|
||||
from apps.payments.models import Payment
|
||||
from apps.users.models import User
|
||||
from core.authentication import create_jwt_token
|
||||
|
||||
|
||||
assert os.environ.get("DJANGO_SETTINGS_MODULE") == "config.settings.test"
|
||||
assert settings.DATABASES["default"]["ENGINE"] == "django.db.backends.sqlite3"
|
||||
|
||||
student = User.objects.get(username="bundle0_student")
|
||||
event = Event.objects.get(slug="bundle0-paid")
|
||||
client = Client()
|
||||
headers = {"HTTP_AUTHORIZATION": f"Bearer {create_jwt_token(student)}"}
|
||||
body = json.dumps({"event_id": event.pk, "description": "Local mocked payment replay"})
|
||||
|
||||
|
||||
def create_payment():
|
||||
return client.post(
|
||||
"/api/payments/create",
|
||||
data=body,
|
||||
content_type="application/json",
|
||||
**headers,
|
||||
)
|
||||
|
||||
|
||||
with (
|
||||
override_settings(ALLOWED_HOSTS=["testserver", "localhost"]),
|
||||
patch("apps.payments.api.views.notify_user"),
|
||||
patch("apps.payments.api.views.send_critical_sms.delay"),
|
||||
patch("apps.payments.api.views.requests.post") as gateway,
|
||||
):
|
||||
gateway.side_effect = RuntimeError("simulated gateway outage")
|
||||
failed_request = create_payment()
|
||||
assert failed_request.status_code == 502, failed_request.content
|
||||
assert not Payment.objects.filter(user=student, event=event).exists()
|
||||
|
||||
gateway.side_effect = None
|
||||
gateway.return_value = Mock()
|
||||
gateway.return_value.json.return_value = {
|
||||
"data": {"code": 100, "authority": "BUNDLE0-CANCEL"}
|
||||
}
|
||||
pending = create_payment()
|
||||
assert pending.status_code == 200, pending.content
|
||||
assert pending.json()["start_pay_url"]
|
||||
canceled = client.get(
|
||||
"/api/payments/callback",
|
||||
{"Authority": "BUNDLE0-CANCEL", "Status": "NOK"},
|
||||
)
|
||||
assert canceled.status_code == 302
|
||||
assert "status=failed" in canceled.url
|
||||
assert Payment.objects.get(authority="BUNDLE0-CANCEL").status == Payment.OrderStatusChoices.CANCELED
|
||||
|
||||
gateway.return_value.json.return_value = {
|
||||
"data": {"code": 100, "authority": "BUNDLE0-SUCCESS"}
|
||||
}
|
||||
pending = create_payment()
|
||||
assert pending.status_code == 200, pending.content
|
||||
gateway.return_value.json.return_value = {
|
||||
"data": {"code": 100, "ref_id": "BUNDLE0-REF"}
|
||||
}
|
||||
paid = client.get(
|
||||
"/api/payments/callback",
|
||||
{"Authority": "BUNDLE0-SUCCESS", "Status": "OK"},
|
||||
)
|
||||
assert paid.status_code == 302
|
||||
assert "status=success" in paid.url
|
||||
assert Payment.objects.get(authority="BUNDLE0-SUCCESS").status == Payment.OrderStatusChoices.PAID
|
||||
assert Registration.objects.get(user=student, event=event).status == Registration.StatusChoices.CONFIRMED
|
||||
duplicate = create_payment()
|
||||
assert duplicate.status_code == 400
|
||||
|
||||
print("Paid replay: gateway outage 502; cancellation redirect; mocked success and confirmed registration; duplicate blocked.")
|
||||
BIN
tasks/evidence/2026-09-27/cms-admin-home-1440.png
Normal file
|
After Width: | Height: | Size: 142 KiB |
BIN
tasks/evidence/2026-09-27/cms-admin-home-390.png
Normal file
|
After Width: | Height: | Size: 103 KiB |
BIN
tasks/evidence/2026-09-27/cms-admin-published-1440.png
Normal file
|
After Width: | Height: | Size: 57 KiB |
BIN
tasks/evidence/2026-09-27/cms-admin-publisher-390.png
Normal file
|
After Width: | Height: | Size: 36 KiB |
@@ -1,5 +1,7 @@
|
||||
# Guilan ACE redesign and public-content CMS: implementation plan
|
||||
|
||||
**CMS course correction (2026-09-27):** The owner replaced this document's draft/publish and flexible-section proposals with the [simple direct-save CMS plan](bundle1-simplification-plan.md). Follow that plan and the updated [checklist](todo.md) for all CMS work. The original decisions below remain historical context for the broader redesign, not active CMS requirements.
|
||||
|
||||
Status: Bundle 0 discovery and design contract approved by the owner on 2026-09-26; product implementation has not started. This plan is shared across the frontend, backend, and deployment repositories. The executable checklist is [todo.md](todo.md). Later implementation checkpoints and production rollout approvals remain required.
|
||||
|
||||
## Outcome and boundaries
|
||||
|
||||
@@ -2,11 +2,11 @@
|
||||
|
||||
This is an **implementation backlog**, with work-in-progress noted below and checkboxes reserved for accepted work. See [plan.md](plan.md) for scope, estimates, migration sequence, and release gates. `F`, `B`, and `D` mean frontend, backend, and deployment repository. Each item is intended to fit one focused session (S = 1–2 files; M = 3–5 files); split any item that grows larger. In addition to each item’s check, apply the definition of done: focused and regression tests, lint/build, runtime/browser check, security/rollback where relevant, and human review.
|
||||
|
||||
Bundle 0 started 2026-09-24 and was approved by the owner on 2026-09-26. Deliverables: [content inventory](content-inventory.md), [baseline](baseline-2026-09-24.md), [local journey replay](evidence/2026-09-24/local-replay.md), [product/asset review](product-review.md), [CMS/API contract](cms-api-contract-draft.md), backend [API schema](../../guilan-ace-backend/docs/content-api-schema-draft.md), and the approved Home/About visual direction linked from frontend `DESIGN.md`. The owner approved the [Home](concepts/d-logo-led-home.html) and [About](concepts/d-logo-led-about.html) direction on 2026-09-25, then approved the [Bundle 0 review](bundle0-review.md) on 2026-09-26, accepting the content boundary, recorded baseline, and CMS/API contract. T01–T05 and Checkpoints A/B are closed at discovery and contract scope. The current pytest suites pass all 237 tests; the earlier 89-error run is preserved as historical evidence. External SMS/OAuth/live-gateway checks, disputed seed claims, and production media/account assignments remain follow-up work in later gates. Bundle 1 started 2026-09-27; T06–T07 await Checkpoint C review.
|
||||
Bundle 0 started 2026-09-24 and was approved by the owner on 2026-09-26. Deliverables: [content inventory](content-inventory.md), [baseline](baseline-2026-09-24.md), [local journey replay](evidence/2026-09-24/local-replay.md), [product/asset review](product-review.md), [CMS/API contract](cms-api-contract-draft.md), backend [API schema](../../guilan-ace-backend/docs/content-api-schema-draft.md), and the approved Home/About visual direction linked from frontend `DESIGN.md`. The owner approved the [Home](concepts/d-logo-led-home.html) and [About](concepts/d-logo-led-about.html) direction on 2026-09-25, then approved the [Bundle 0 review](bundle0-review.md) on 2026-09-26, accepting the content boundary, recorded baseline, and CMS/API contract. T01–T05 and Checkpoints A/B are closed at discovery and historical contract scope. The current pytest suites pass all 237 tests; the earlier 89-error run is preserved as historical evidence. External SMS/OAuth/live-gateway checks, disputed seed claims, and production media/account assignments remain follow-up work in later gates. Bundle 1 started 2026-09-27; Checkpoint C was approved by the owner on 2026-09-27. The owner changed the CMS direction on 2026-09-27; the active scope is [simple direct-save content editing](bundle1-simplification-plan.md), which supersedes the earlier draft/publish contract.
|
||||
|
||||
## 0. Discovery and design contract (4–5 person-days)
|
||||
|
||||
- [x] **T01 — Classify static public content (M; deps: none).** Files: F `src/views/*`, `src/components/Navbar.tsx`, `Footer.tsx`, `src/app/*`. Acceptance: every public route/string/image/SEO field is mapped to CMS editorial, existing domain data, or code-owned functional UI; conflicts and asset ownership are flagged. Verify: owner reviews the manifest against live desktop/mobile pages. Owner accepted the content boundary and exclusions in `bundle0-review.md` on 2026-09-26; uncertain claims remain excluded from the initial seed.
|
||||
- [x] **T01 — Classify static public content (M; deps: none).** Files: F `src/views/*`, `src/components/Navbar.tsx`, `Footer.tsx`, `src/app/*`. Acceptance: every public route/string/image/SEO field is mapped to CMS editorial, existing domain data, or code-owned functional UI; conflicts and asset ownership are flagged. Verify: owner reviews the manifest against live desktop/mobile pages. Owner accepted the content boundary and exclusions in `bundle0-review.md` on 2026-09-26. The unchanged `0005` migration retains legacy About affiliations and years; the mock-content command replaces that copy on untouched records before CMS rendering.
|
||||
- [x] **T02 — Capture flow and performance baseline (M; deps: none).** Files: D `tasks/` evidence, F test artifacts. Acceptance: route/state map covers auth, event registration/payment, blog, and staff admin; screenshots and bundle/accessibility baselines have dated device widths. Verify: manually replay each existing happy path and one failure path on staging/local. Owner accepted the recorded local baseline on 2026-09-26 with the historical test run and unexercised external paths documented; the current pytest suites passed on 2026-09-26 (see rerun record).
|
||||
- [x] **Checkpoint A (deps: T01–T02).** Content categories, current behavior, and baseline evidence reviewed; no business-rule changes inferred. Accepted 2026-09-26 with the limits documented in `bundle0-review.md`.
|
||||
- [x] **T03 — Review product truth and assets (S; deps: T01).** Files: F `PRODUCT.md`, asset inventory. Acceptance: initial product record is checked against source assets and owner input; logo sources are cataloged; conflicting contacts/statistics remain pending rather than guessed. Verify: owner signs off facts and supplies approved contact channels before production seed publication. Concept-scope sign-off: owner confirmed repository facts/assets and chose About-page contacts; claim-to-entity mapping remains a T12 seed gate.
|
||||
@@ -16,43 +16,42 @@ Bundle 0 started 2026-09-24 and was approved by the owner on 2026-09-26. Deliver
|
||||
|
||||
## 1. CMS foundation and migration (9–12 person-days)
|
||||
|
||||
Work in progress: T06–T07 implementation and verification began 2026-09-27. See [Checkpoint C review](bundle1-checkpoint-c-review.md). Acceptance checkboxes remain open pending owner review; T08 onward has not started.
|
||||
Checkpoint C was approved by the owner on 2026-09-27 after reviewing [the schema and validation record](bundle1-checkpoint-c-review.md). The old T08–T09 draft/publish workflow was rejected. The direct-save Unfold admin was reviewed and approved by the owner on 2026-09-27; the old [Checkpoint D review](bundle1-checkpoint-d-review.md) is superseded. The current-content API and Checkpoint E were approved on 2026-09-27. Follow the [course correction plan](bundle1-simplification-plan.md); the seeded About content review is documented in [Checkpoint F review](bundle1-seed-review.md).
|
||||
|
||||
- [ ] **T06 — Add site identity and page schema (M; deps: T05).** Files: B new content app models/migration/tests. Acceptance: one site identity, stable page slugs, ordered typed sections, image/alt and SEO fields exist with uniqueness/validation; migrations are additive. Verify: migration test and `python manage.py check` on a test database.
|
||||
- [ ] **T07 — Validate fields and media handling (M; deps: T06).** Files: B content validators/tests. Acceptance: URLs, section payloads, image type/size, alt text, and unsafe markup are constrained; absent optional media renders safely. Verify: invalid/oversize/malicious inputs rejected in focused tests.
|
||||
- [ ] **Checkpoint C (deps: T06–T07).** Schema and validation reviewed before permissions/API work.
|
||||
- [ ] **T08 — Implement draft, publish, and revision permissions (M; deps: T06).** Files: B content services/permissions/tests. Acceptance: staff draft, publisher publish/restore, superuser override, audit trail, and atomic published snapshot; unauthorized edits/publish fail. Verify: role-matrix and concurrent-publish tests.
|
||||
- [ ] **T09 — Configure Django admin editing workflow (M; deps: T07–T08).** Files: B content admin/forms/tests. Acceptance: editors can edit/reorder/hide only defined sections, see validation, preview draft, and identify published revision without raw JSON editing. Verify: hands-on staff/publisher Django admin walkthrough at desktop and phone widths.
|
||||
- [ ] **Checkpoint D (deps: T08–T09).** Draft cannot leak or publish by the wrong role.
|
||||
- [ ] **T10 — Expose public published-content API (M; deps: T08).** Files: B content Ninja router/schemas/tests, `config/api.py`. Acceptance: only published identity/pages appear; missing content has explicit behavior; response shape and cache headers are documented. Verify: anonymous API tests including unpublished-only data.
|
||||
- [ ] **T11 — Expose authenticated no-store preview (M; deps: T08, T10).** Files: B preview route/auth/tests. Acceptance: approved staff can preview exact draft, anonymous callers cannot, and response is uncached. Verify: permission/cache tests and a real preview URL walkthrough.
|
||||
- [ ] **Checkpoint E (deps: T10–T11).** API contract frozen for frontend integration.
|
||||
- [ ] **T12 — Produce versioned seed manifest (M; deps: T01, T06).** Files: B content seed data, D migration notes. Acceptance: stable keys, exact legacy text, source paths, media checksums, and owner-approval status documented; disputed contacts/counts are excluded from automatic publication. Verify: manifest diffed against current live pages/code and signed by content owner.
|
||||
- [ ] **T13 — Build idempotent dry-run seed command (M; deps: T12).** Files: B management command/tests. Acceptance: prints create/skip/conflict and field diffs, supports no-write mode, never overwrites editor changes by default, and seeds approved entries as drafts. Verify: run twice on a database copy; second run is no-op; altered content reports conflict.
|
||||
- [ ] **Checkpoint F (deps: T12–T13).** Seed and approval behavior demonstrated on staging-sized data before production use.
|
||||
- [ ] **T14 — Add frontend CMS adapter and cutover flag (M; deps: T10).** Files: F content fetch/types/flag tests. Acceptance: published content can be fetched server-side with typed fallback; flag defaults to legacy rendering until launch; preview is never cached publicly. Verify: integration tests with CMS available, unavailable, and stale response.
|
||||
- [x] **T06 — Add site identity and page schema (M; deps: T05).** Files: B new content app models/migration/tests. Acceptance: one site identity, stable page slugs, ordered typed sections, image/alt and SEO fields exist with uniqueness/validation; migrations are additive. Verify: migration test and `python manage.py check` on a test database.
|
||||
- [x] **T07 — Validate fields and media handling (M; deps: T06).** Files: B content validators/tests. Acceptance: URLs, section payloads, image type/size, alt text, and unsafe markup are constrained; absent optional media renders safely. Verify: invalid/oversize/malicious inputs rejected in focused tests.
|
||||
- [x] **Checkpoint C (deps: T06–T07).** Schema and validation reviewed before permissions/API work. Approved by the owner on 2026-09-27.
|
||||
- [x] **T08 — Audit current CMS data and permissions (S; deps: T06–T07).** Completed against the owner-authorized local development PostgreSQL database on 2026-09-27; see [database audit](bundle1-development-db-audit.md). Content migrations `0001`–`0005` are applied; the five current page/site records exist, with no media references. Home has two admin changes; the other content records do not. One staff account exists and is the only superuser; the legacy editor/publisher groups have no members and no users have direct content permissions. The old revision tables contain no rows. Staging was not needed or accessed; production permissions remain a release-gate audit.
|
||||
- [x] **T09 — Simplify content schema and use native Unfold forms (M; deps: T08).** Files: B content models/migrations/admin/forms/tests. Acceptance: one editable record per fixed page and shared site identity; explicit text/image/alt/SEO fields; direct Save; standard Django model change permissions; no arbitrary page creation, custom admin templates, draft/revision/approval workflow, or delete action. Verified: backend suite passed 248 tests; Django and migration checks passed; owner reviewed and approved the Unfold admin on 2026-09-27. The development database audit is complete; production access remains a release gate.
|
||||
- [x] **Checkpoint D (deps: T09).** Owner approved the direct-save Unfold forms and permissions on 2026-09-27. The old draft/publish review is superseded.
|
||||
- [x] **T10 — Expose current-content read API (M; deps: T09).** Files: B content Ninja router/schemas/tests, `config/api.py`; contract: backend [content API v1 note](../../guilan-ace-backend/docs/content-api-v1.md). Acceptance: shared site record and four fixed pages expose current content and SEO, with explicit missing/unknown behavior and no revision/status/preview fields. Verified: anonymous read, save-then-read, missing content, and cache behavior; backend suite passed 255 tests and Django checks passed. Owner approved the API checkpoint on 2026-09-27.
|
||||
- [x] **Checkpoint E (deps: T10).** The current-content API shape was checked and approved by the owner on 2026-09-27. No private preview API is planned.
|
||||
- [x] **T12 — Review starting content and media (M; deps: T01, T09).** Files: D source review, B initial content data. Owner reviewed the source content and approved blank CMS image fields, then directed that mock copy be used until staff save content. No current-site claims or media are in the mock content.
|
||||
- [x] **T13 — Initialize fixed content records safely (S; deps: T12).** Files: B management command/tests; do not change existing migrations. Implemented as `python manage.py seed_mock_content`, with `--dry-run` and `--database` options. Three focused command tests passed. On the owner-authorized development database, the dry run proposed four untouched records and skipped Home because of two admin changes; execution updated Site, About, Events, and Blog, preserved Home, and created six mock About items. See [database audit](bundle1-development-db-audit.md). No migration was changed.
|
||||
- [x] **Checkpoint F (deps: T12–T13).** Owner reviewed and approved the mock starting content and blank optional image fields; the development database seed result is recorded in [database audit](bundle1-development-db-audit.md).
|
||||
- [x] **T14 — Add frontend CMS adapter and cutover flag (M; deps: T10).** Files: F `src/lib/content-cms.ts`, `.env.sample`. Acceptance: current content can be fetched server-side with typed mock fallback; blank CMS fields and request failures use mock copy; the flag defaults off; `no-store` fetches expose the latest admin save. The adapter returns mock values rather than caller-provided legacy copy. Frontend lint and production build passed; pages remain on legacy rendering until later tasks connect them.
|
||||
|
||||
## 2. Home/About and shared public shell (9–12 person-days)
|
||||
|
||||
- [ ] **T15 — Establish approved visual tokens and shell (M; deps: T05, T14).** Files: F global styles, fonts, navigation/footer components. Acceptance: approved typography/colors/layout, responsive header/footer, and CMS identity/navigation/contact fields match comps; RTL/keyboard/reduced-motion work. Verify: screenshot comparison at 320/390/768/1440px and keyboard pass.
|
||||
- [ ] **T16 — Build CMS-driven Home editorial sections and domain previews (M; deps: T15).** Files: F Home view, section components, tests. Acceptance: hero/featured-section/CTA text comes from published CMS in the fixed owner-approved order; event and blog cards read existing domain APIs with truthful status, useful links, and sound empty/long layouts. Verify: editor text change appears on preview then public page only after publish; event/blog source changes update previews; visual review at all widths.
|
||||
- [ ] **Checkpoint G (deps: T15–T16).** Home concept fidelity and real content signed off before About.
|
||||
- [ ] **T17 — Build CMS-driven About sections (M; deps: T15).** Files: F About view, section components, tests. Acceptance: official identity, mission, committee/association facts, and contacts come from approved published data; unsupported claims are absent. Verify: compare rendered content to approved manifest and test visibility/reorder.
|
||||
- [ ] **T18 — Make page SEO/structured data content-driven (M; deps: T16–T17).** Files: F route metadata/SEO utilities/tests. Acceptance: canonical, title, description, OG image, and structured-data identity are consistent with published CMS and server-rendered HTML; no stale hardcoded names. Verify: inspect HTML/JSON-LD and link previews on staging.
|
||||
- [ ] **T19 — Finish Home/About interaction and accessibility pass (M; deps: T16–T18).** Files: F affected page/styles/tests. Acceptance: motion supports comprehension but does not block content, reduced-motion works, contrast/focus/read order and Persian line wraps pass. Verify: real browser desktop/mobile captures, keyboard, screen-reader spot check, and Impeccable critique against approved comp.
|
||||
- [ ] **Checkpoint H (deps: T17–T19).** Owner approves Home/About on staging; content editors can make and publish a visible change without developer help.
|
||||
- [x] **T15 — Establish approved visual tokens and shell (M; deps: T05, T14).** Files: F global styles, fonts, navigation/footer components. Acceptance: approved typography/colors/layout, responsive header/footer, and CMS identity/navigation/contact fields match comps; RTL/keyboard/reduced-motion work. Implementation reviewed and approved by the owner at Checkpoint G on 2026-09-28.
|
||||
- [x] **T16 — Build CMS-driven Home editorial sections and domain previews (M; deps: T15).** Files: F Home view, section components, tests. Acceptance: hero/featured-section/CTA text comes from the current CMS record in the fixed owner-approved order; event and blog cards read existing domain APIs with truthful status, useful links, and sound empty/long layouts. Home was reviewed and approved by the owner at Checkpoint G on 2026-09-28; frontend lint and production build passed.
|
||||
- [x] **Checkpoint G (deps: T15–T16).** Owner approved the Home design and content at target widths on 2026-09-28. T17 can proceed.
|
||||
- [x] **T17 — Build CMS-driven About sections (M; deps: T15).** Files: F About view, section components, tests. Acceptance: owner-approved About identity, purpose, member-year archive, selected event highlights, separate memories gallery, and general history timeline come from current CMS records; unsupported claims are absent. The development frontend now reads the CMS API, with mock values filling empty fields; the admin Save-to-public change remains for owner review at Checkpoint H.
|
||||
- [x] **T18 — Make page SEO/structured data content-driven (M; deps: T16–T17).** Files: F route metadata/SEO utilities/tests. Acceptance: canonical, title, description, OG image, and structured-data identity are consistent with current CMS records and server-rendered HTML; no stale hardcoded names. SSR checks passed for Home, About, Events, Blog, event details, and post details; filtered listings are noindex with their base page as canonical.
|
||||
- [x] **T19 — Finish Home/About interaction and accessibility pass (M; deps: T16–T18).** Files: F affected page/styles/tests. Acceptance: motion supports comprehension but does not block content, reduced-motion works, contrast/focus/read order and Persian line wraps pass. Added the shared skip link, semantic main landmarks for listing routes, labeled search controls, and valid ordered-list markup; global focus and reduced-motion rules remain active. Owner visual/keyboard review is part of Checkpoint H because the in-app browser connection could not be established.
|
||||
- [x] **Checkpoint H (deps: T17–T19).** Owner approved Home/About on the CMS-enabled development frontend, including keyboard/focus and mobile review, on 2026-09-28.
|
||||
|
||||
## 3. Authentication (5–7 person-days)
|
||||
|
||||
- [ ] **T20 — Simplify auth entry and navigation (M; deps: T15, T02).** Files: F Auth view/auth shell/tests. Acceptance: login/signup choice, intent, next step, and back path are clear on phone/desktop without changing backend auth rules. Verify: first-time student and returning-user walkthroughs.
|
||||
- [ ] **T21 — Clarify form, OTP, loading, and error states (M; deps: T20).** Files: F auth components/tests. Acceptance: each step has one clear action, input constraints and errors are accessible, resend/countdown are understandable, and retry does not lose valid input. Verify: keyboard, slow-network, invalid-input, and duplicate-submit tests.
|
||||
- [ ] **Checkpoint I (deps: T20–T21).** Core login/signup behavior unchanged and clear in RTL/mobile.
|
||||
- [ ] **T22 — Align recovery and verification screens (M; deps: T20).** Files: F reset/verify/callback views/tests. Acceptance: expired token, failed Google callback, success, and return-to-intent states follow same system; no dead end. Verify: browser tests for link/token variants.
|
||||
- [ ] **T23 — Auth regression gate (M; deps: T21–T22).** Files: F end-to-end tests, B focused auth tests if needed. Acceptance: login, signup, verification, reset, logout, and protected redirect pass with existing API semantics. Verify: full focused automated suite plus manual mobile smoke.
|
||||
- [x] **T20 — Simplify auth entry and navigation (M; deps: T15, T02).** Files: F Auth view/auth shell/tests. Acceptance: mobile entry automatically routes password accounts to password entry, passwordless existing accounts to login OTP, and new numbers to registration OTP then details, with a clear way to edit the phone and no backend auth-rule changes. Implemented this single phone-first path and removed the separate login/signup selector.
|
||||
- [x] **T21 — Clarify form, OTP, loading, and error states (M; deps: T20).** Files: F auth components/tests. Acceptance: each step has one clear action, input constraints and errors are accessible, resend/countdown are understandable, and retry does not lose valid input. Added inline announced errors, field autocomplete/constraints, an accessible OTP label, cooldown display, duplicate-submit protection, and automatic sign-in after successful registration. Removed forgotten-password UI because OTP login covers account access, improved dark-mode error contrast, and standardized outline/toggle/nav and account-menu hovers on the theme's secondary color. Frontend lint/build pass.
|
||||
- [x] **Checkpoint I (deps: T20–T21).** Owner reviewed and approved the phone → password/OTP → registration details flow, dark-mode errors, and button hover colors in RTL/mobile on 2026-09-28; backend auth rules remain unchanged.
|
||||
- [x] **T22 — Align verification and OAuth callback screens (M; deps: T20).** Files: F verification/callback views/tests. Acceptance: expired token, failed Google callback, success, and return-to-intent states follow the same system; forgotten-password recovery stays removed in favor of OTP login. Added persistent accessible Persian errors for incomplete/expired Google callbacks and preserved safe internal return destinations through Google login and recovery. Browser smoke confirmed the callback error state at 390px in dark mode. The error icon and text follow RTL flex-start alignment with the accessible red/dark error treatment.
|
||||
- [x] **T23 — Auth regression gate (M; deps: T21–T22).** Files: F end-to-end tests, B focused auth tests if needed. Acceptance: password login, OTP login, signup, verification, logout, protected redirects, and the retired recovery URL behave with existing API semantics. Added 4 safe-return-path unit tests. Password login submits the selected mobile as name=username with autocomplete=username in the same form as current-password; browser DOM confirmed the field metadata; frontend auth tests, lint, and production build pass; 43 focused backend auth/OAuth tests pass with isolated SQLite. Browser smoke confirmed protected /profile returns to /auth?next=%2Fprofile, /reset-password returns to /auth, and expired Google callback displays its error state. No live SMS/OAuth sign-in was exercised. Next.js upgraded to 16.3.5 for the runtime verification tooling. Existing lockfile audit reports 11 production dependency advisories (1 critical, 5 high); dompurify remediation requires a breaking jspdf upgrade and is deferred to T37.
|
||||
|
||||
## 4. Events and registration (9–12 person-days)
|
||||
|
||||
- [ ] **T24 — Redesign event discovery (M; deps: T15, T02).** Files: F Events view/cards/tests. Acceptance: upcoming/past/full states, date/location/price, filters, and empty/error states are clear without changing query semantics; editorial page intro/SEO reads the published CMS record. Verify: fixture-based state matrix at phone/desktop widths and a CMS publish check.
|
||||
- [ ] **T24 — Redesign event discovery (M; deps: T15, T02).** Files: F Events view/cards/tests. Acceptance: upcoming/past/full states, date/location/price, filters, and empty/error states are clear without changing query semantics; listing intro/SEO reads the current CMS record. Verify: fixture-based state matrix at phone/desktop widths and an admin Save check.
|
||||
- [ ] **T25 — Redesign event detail and eligibility explanation (M; deps: T24).** Files: F EventDetail view/test. Acceptance: schedule, capacity/availability, prerequisites, organizer, and call to action match existing backend data and rules; no invented promise. Verify: compare UI against API fixtures for open/closed/full/free/paid cases.
|
||||
- [ ] **Checkpoint J (deps: T24–T25).** Student can decide whether an event is relevant before entering registration.
|
||||
- [ ] **T26 — Clarify registration forms and transitions (M; deps: T25).** Files: F event form/registration tests. Acceptance: field requirements, validation, duplicate/closed/full responses, and retry are legible; requests and eligibility rules unchanged. Verify: browser/API contract tests for every backend error/status.
|
||||
@@ -63,12 +62,12 @@ Work in progress: T06–T07 implementation and verification began 2026-09-27. Se
|
||||
## 5. Blog (4–6 person-days)
|
||||
|
||||
- [ ] **T29 — Audit existing blog before editing (S; deps: T15, T02).** Files: F blog audit notes/tests. Acceptance: existing strengths and gaps in discovery, article typography, media, SEO, and interactions are recorded; no unnecessary rewrite. Verify: editorial and reader walkthrough on mobile/desktop.
|
||||
- [ ] **T30 — Apply selected blog refinements (M; deps: T29).** Files: F Blog/list/detail components/tests. Acceptance: visual system is coherent, article reading and navigation improve, existing URLs/actions/content remain intact; editorial listing intro/SEO reads the published CMS record. Verify: screenshot/keyboard review with long Persian article and code block plus a CMS publish check.
|
||||
- [ ] **T30 — Apply selected blog refinements (M; deps: T29).** Files: F Blog/list/detail components/tests. Acceptance: visual system is coherent, article reading and navigation improve, existing URLs/actions/content remain intact; listing intro/SEO reads the current CMS record. Verify: screenshot/keyboard review with long Persian article and code block plus an admin Save check.
|
||||
- [ ] **T31 — Blog regression gate (M; deps: T30).** Files: F blog tests/SEO. Acceptance: listing, category/tag navigation, article render, share/interactions, and metadata still work. Verify: browser and API contract checks.
|
||||
|
||||
## 6. Frontend staff admin (7–10 person-days)
|
||||
|
||||
- [ ] **T32 — Map staff roles and mobile task priorities (M; deps: T02, T15).** Files: F admin IA/spec. Acceptance: top tasks for event manager, blog editor, publisher, and general staff are documented; access is not confused with visual hiding. Verify: role-based task walkthrough with actual members.
|
||||
- [ ] **T32 — Map staff roles and mobile task priorities (M; deps: T02, T15).** Files: F admin IA/spec. Acceptance: top tasks for event manager, blog editor, content editor, and general staff are documented; access is not confused with visual hiding. Verify: permission-based task walkthrough with actual members.
|
||||
- [ ] **T33 — Restructure admin navigation and overview (M; deps: T32).** Files: F AdminLayout/Dashboard/tests. Acceptance: navigation and summaries work at phone/tablet/desktop, with no off-screen controls or meaningless dashboard clutter. Verify: responsive screenshots and keyboard navigation.
|
||||
- [ ] **Checkpoint L (deps: T32–T33).** At least one staff member completes top tasks at 390px and desktop widths.
|
||||
- [ ] **T34 — Make tables, forms, and confirmations responsive (M; deps: T33).** Files: F admin event/user/blog shared patterns/tests. Acceptance: filters, bulk actions, destructive confirmations, and long forms remain visible and operable at 320px; dense data has a deliberate mobile pattern. Verify: real-device/viewport walkthrough of create/edit/search/confirm tasks.
|
||||
@@ -78,7 +77,7 @@ Work in progress: T06–T07 implementation and verification began 2026-09-27. Se
|
||||
## 7. Release hardening and production cutover (4–7 person-days)
|
||||
|
||||
- [ ] **T37 — Cross-site quality gate (M; deps: T19, T23, T28, T31, T36).** Files: F/B tests and D QA record. Acceptance: lint/build/tests pass, critical routes meet accessibility/SEO/performance budgets against baseline, no critical mobile/RTL defect remains. Verify: CI plus Chrome/Firefox/Safari-equivalent checks where available and reduced-motion/keyboard review.
|
||||
- [ ] **T38 — Rehearse migration, backup, and rollback (M; deps: T13, T37).** Files: D runbook/scripts, B seed command. Acceptance: staging clone has tested `migrate`, media stage, seed dry-run/apply, restore of published revision, frontend flag rollback, and verified backup recovery path. Verify: record commands, checksums, timings, and pass/fail evidence.
|
||||
- [ ] **T38 — Rehearse migration, backup, and rollback (M; deps: T13, T37).** Files: D runbook/scripts, B content initialization. Acceptance: staging clone has tested `migrate`, media stage, safe content initialization, frontend flag rollback, and verified backup recovery path. Verify: record commands, checksums, timings, and pass/fail evidence.
|
||||
- [ ] **Checkpoint M (deps: T37–T38).** Owner approves release checklist; production remains untouched until gate passes.
|
||||
- [ ] **T39 — Production CMS seed and phased cutover (M; deps: T38).** Files: D runbook/observability, production state. Acceptance: verified fresh backup, additive backend migration, approved assets/drafts, publisher approval, flagged frontend release, and smoke checks occur in order. Verify: compare public text/SEO to approved manifest; check health, API errors, media, auth, registration/payment signals.
|
||||
- [ ] **T39 — Production CMS initialization and phased cutover (M; deps: T38).** Files: D runbook/observability, production state. Acceptance: verified fresh backup, safe backend migration, approved current content/assets, flagged frontend release, and smoke checks occur in order. Verify: compare public text/SEO to reviewed starting values; check health, API errors, media, auth, registration/payment signals.
|
||||
- [ ] **T40 — Observe and close release (S; deps: T39).** Files: D release notes/incident log. Acceptance: defined observation window ends without unresolved critical defect; known issues and next-phase messaging/business-rule requests are recorded. Verify: owner signs off live mobile/desktop journeys and rollback readiness.
|
||||
|
||||