2026-09-09 17:07:55 +00:00
# APP.yaml
#
# Single source-of-truth specification for the app, organized by feature/domain area.
# Built per .junie/guidelines.md (authoring phase). MILESTONES.yaml is written from this
# file afterward — do not create/update it until asked.
#
# Section conventions (use within any section as needed):
# notes: free-form rationale, considerations, examples (prose or short list)
# decisions_log: append-only list of resolved questions -> {question, decision, rationale}
# open_questions: structured list of items still to clarify -> {question, options?}
# (empty list when none remain)
#
# This is a skeleton layout. Sections below are placeholders to be filled in and adapted
# (added/removed/renamed) as the project's ideas are gathered.
---
app :
name : SiteWeaverCMS
2026-09-20 05:13:41 +00:00
use_milestones : true
2026-09-09 17:07:55 +00:00
description : >
A new PHP CMS, whose goal is to be modular, with easy to build and understand templates,
built-in module installer (using Zip file uploader),
with an admin interface even a non-tech person finds inviting.
notes :
- Publicly viewable front-end (posts, pages)
- Auth required back-end (Media manager, User manager, Role-based Auth manager, Post/Page manager, Theme manager, Plugin/Module manager)
- URL should be in the form of `https://domain.tld/{page-slug}`
- Security & Reliability :
- Built-in 2FA (TOTP/Email)
- Automatic Basic Security Audits for Plugins :
- no eval
- no exec
- no remote code execution
- Content Management :
- Primary language configurable in Configuration Settings
- Configuration settings to list/check/select which other languages the site supports
- Content Versioning & Rollbacks (History)
- Rich Text Editor (producing clean HTML)
- Built-in multi-language support (i18n)
- i18n Translation Engine :
- Database-backed with `t()` (or t-like method) helper for UI strings.
- Multi-Locale Content Support :
2026-09-20 05:13:41 +00:00
- storage for Pages, Posts, and Media.
2026-09-09 17:07:55 +00:00
- Translate for Me Service :
- Service to auto-translate Post/Page/etc to languages selected in Configuration Settings
- Button to trigger Service
2026-09-20 05:13:41 +00:00
- Process :
- Text of Page/Post is sent to a self-hosted LibreTranslate instance, along with the list of approved languages
- LibreTranslate instance returns the translated text
- Translated text is stored in the {Page|Post}_Versions table, with the language identifier in the `locale` column
- composer_require :
- composer require jefs42/libretranslate
decisions_log_ref : ".decisions/app_decisions.yaml"
2026-09-09 17:07:55 +00:00
open_questions : [ ]
suggestions : [ ]
glossary :
# Domain-specific terms and their meanings, so everyone uses the same vocabulary.
terms :
- term : $theme variable
definition : >
An object variable exposed to themes, containing the metadata described in
the theme.md (or admin.theme.md) file. This will include Theme Name, Author,
Description, Date, Version. Inside the theme, it is accessed as
$theme->author, $theme->name, etc
- term : 2FA (Two-Factor Authentication)
definition : >
A login security step requiring a second verification factor (e.g., a TOTP
code or an emailed code) in addition to the password.
- term : Admin Theme
definition : >
A theme to alter the look and feel of the Admin section of the CMS. This does
not alter the frontend that visitors to the site will see. This only alters
the backend admin section.
- term : Automated Cache Warming on Publish
definition : >
When a Page or Post is published, the system will pre-cache the Page/Post,
so the visitors will always receive the fastest response (a cached page/post
vs rendered-on-demand page/post)
- term : Blog Post Author Permissions
definition : >
The permissions granted to the author of a blog post (the Author role).
This includes things like Publishing, Editing, Un-Publishing, Deleting,
Saving (prior to publish), Allowing comments on Post
- term : Comments System
definition : >
The functionality of the CMS dedicated to the comment section. This allows
Users to post comments (when the Author has turned comments on), but does
its best to deter spammers, scammers, etc.
- term : Content Versioning & Rollbacks (History)
definition : >
Everytime a Page or Blog Post is saved, a revision is saved in the database.
This way, if the altered changes are not wanted or unapproved, the Page/Post
can be rolled back to a previous iteration easily.
- term : CRUD
definition : >
Create, Read, Update, Delete — the four basic operations for managing a
resource/record.
- term : Faceted Search
definition : >
A search interface combining free-text search with multiple simultaneous
filters (facets) - e.g. categories/tags and date ranges - that narrow
results as they're applied.
- term : Focal Point Selector
definition : >
Selecting the focus point of an image. When an image is cropped or overflow hidden,
this is the "center" of what is shown. It is the focal point.
- term : Frontend Theme
definition : >
A theme to alter the look and feel of the frontend of the CMS. This alters
the frontend that visitors to the site will see. This does not alter the
backend admin section.
- term : Granular Permissions
definition : >
Role-based authorization. Setting the permissions each role has the capability
to perform. Creating, Reading, Updating, Deleting each individual section or
area of the CMS (blog posts, pages, users, media, etc)
- term : Hooks (theme.md)
definition : >
The section of theme.md (the theme metadata file) that tells the theme renderer
what asset files (css, js) need to be loaded and where (header, footer, attached
to partials, etc)
- term : i18n
definition : >
Internationalization — designing/building software so it can be adapted to
different languages/locales without changing the code.
- term : i18n Translation Engine
definition : >
This is the engine that drives the internationalization. When the content on the site
is translated from one language to another, this is the engine that does the translating.
- term : JIT (Just-In-Time) Generation
definition : >
Generating a derived asset (e.g. a resized/cropped image variant) on demand
at request time, instead of pre-generating every possible variant in advance.
- term : Modular DB Migrations
definition : >
Every Plugin/Module is responsible for its own database migrations. What tables
or columns in a table that it relies on to be present in order to run.
- term : Multi-Locale Content Support
definition : >
Storing Pages, Posts, and Media content as JSON-per-locale, so each supported
language/locale has its own independently maintained version of a resource.
- term : Orphaned Media Watcher
definition : >
A background service that scans the entries in the Media Manager, and scans the contents
of Pages and Posts to find when certain media entries are no longer being used. If a media
entry has been orphaned (no longer in use), it is a candidate for deletion, to save space
on the server.
- term : Page Builder Core
definition : >
The CMS is a robust Page Builder. Whether that page is a basic web page (Home, About Us, etc)
a list of Blog Entries, a Calendar, etc, it is all about the Page.
- term : Partial (template partial)
definition : >
A small portion of the overall page. The navbar on the webpage, if separated out into its
own template would be a good example. If you separate the top, bottom, and "main" sections
of a webpage out into their own template files, each of those files would be partials, that
are then rendered into one larger full template file.
- term : PDO
definition : >
PHP Data Objects — PHP's built-in database access layer providing a common
interface across different database engines.
- term : Preview Cache Warming
definition : >
After saving a Page or Post alteration, you are able to Preview how it looks prior to publishing
it to Production. When you click the link to preview it, the system will cache it, so that when you
Publish it, the cache is already present for when your visitors want to view the page. If you alter
the page/post further, before publishing, the cache will be rendered stale, and will be overwritten
by the next Preview Cache Warming or Publish Cache Warming.
- term : Rich Text Editor
definition : >
A WYSIWYG (What You See Is What You Get) editor for formatting text content
that produces clean HTML markup.
- term : Service Provider
definition : >
A class responsible for registering/bootstrapping a plugin's or component's
functionality (routes, migrations, admin sections, etc.) with the core
application.
- term : Site backup
definition : >
This is a process, kicked off in the admin section, to backup the site files and database.
- term : Storage Interface / Storage Abstraction
definition : >
A common interface (`StorageInterface::get($path)`, `put($path, $data)`,
`delete($path)`) that each storage backend (Local Disk, S3, Nextcloud, Linode
Object-Storage) implements. The rest of the CMS (media upload, JIT generation,
etc.) talks only to this interface, making it easy to swap or add storage
providers without touching the calling code.
- term : theme.md / admin.theme.md
definition : >
These are the metadata files for the CMS frontend themes and admin themes. They will contain
information on the theme name, author, date, descriptions, hooks, etc
- term : Theme Editor
definition : >
This is a built-in file editor, in the admin section, that allows an Admin or other authorized
role to make minor modifications to the theme files without the need to change the files
physically and upload them to the server. The theme files are edited in place on the server.
- term : TOTP
definition : >
Time-based One-Time Password — a short-lived numeric code generated from a
shared secret and the current time, used as a second authentication factor.
notes : [ ]
2026-09-20 05:13:41 +00:00
decisions_log_ref : ".decisions/glossary_decisions.yaml"
2026-09-09 17:07:55 +00:00
open_questions : [ ]
scope :
in_scope :
- Page Builder
- Role Based Auth
- User Management
- Blog plugin
- Media Manager
2026-09-20 05:13:41 +00:00
- Theme/Plugin Management
2026-09-09 17:07:55 +00:00
- i18n
- Comments
2026-09-20 05:13:41 +00:00
- Site Backup
- Site Restore
2026-09-09 17:07:55 +00:00
- Search
out_of_scope :
- Multi-tenancy (SaaS)
- Hosted/SaaS offering
- Mobile app
- API-only approach
architecture :
notes :
- Architecture & Scalability :
- Single-site CMS, no multi-tenancy (see decisions_log)
description : >
2026-09-20 05:13:41 +00:00
SiteWeaverCMS is a Laravel 13.x-based CMS with a modular architecture, designed for
2026-09-09 17:07:55 +00:00
self-hosted use. It features a clean separation of concerns between the core system
and plugins/themes, with a focus on developer productivity and extensibility.
2026-09-20 05:13:41 +00:00
decisions_log_ref : ".decisions/architecture_decisions.yaml"
2026-09-09 17:07:55 +00:00
open_questions : [ ]
plugins_architecture :
description : >
The plugin architecture allows for extending the core functionality of SiteWeaverCMS
through modular components. Each plugin can register its own routes, services, and
configuration settings.
notes :
2026-09-20 05:13:41 +00:00
- plugin_metadata_spec :
required_fields :
- name
- author
- description
- date
- version
- core_version
format :
core_version : semantic version string (e.g., "13.x")
- Plugins :
2026-09-09 17:07:55 +00:00
- Admin section Plugins List Page :
- Lists each plugin (its name, brief description, and active status) :
- Active : Plugin is installed and switched on
- Deactivated : Plugin is installed, but switched off
- Install - Error : Plugin threw an error during Install
- Clicking on Plugin name sends you to Plugin Detail Page for that Plugin
- Plugin Detail Page :
- Lists Plugin metadata details (plugin.md)
- Toggle to switch Plugin Active/Deactivated status
- Service Provider-based architecture
- All plugins, officially included by default or not, reside in the `site/plugins` directory, and are built as a plugin.
- Built-in Plugin Support :
- Blog (included by default) :
- URL should be in the form of `https://domain.tld/{published-4-digit-year}/{published-2-digit-month}/{published-2-digit-day}/{post-title-slug}`
- Example https://example.com/2026/08/26/my-new-dog
- The Published Date for the post is always the published date of the original version. Follow up versions keep the same published date.
- Blog Category Pages :
- URL should be in the form of `https://domain.tld/category/{blog_category_slug}`
- Shows a list of Blog Posts that are tagged with the category in the url
- Blog Posts MUST belong to at least 1 category
- A "Uncategorized" blog category will exist
- Blog Posts do not need to have Blog Tags, but may have many
- Blog Category vs Blog Tag :
- Categories :
- Broad, high‑ level groupings that organize a blog’ s content into major topics.
- Affects URL, changes are rare and deliberate
- Names are meant to be stable, descriptive, and representative of major topics (e.g., Travel, Food, Finance).
- Tags :
- Specific, descriptive keywords that indicate particular subjects, themes, or details within a post.
- Changes are easy, and do not affect URLs.
- Names can be more creative or specific (e.g., vegan, budget‑ travel, 2024‑ updates).
- Blog Category Examples :
- Technology
- Programming
- Front-End
- Vehicles
- Blog Tag Examples :
- ReactJS
- SEO
- Ford Mustang
- Blog Post Comments :
- comments are auto-approved (no manual approval needed), but may be deleted/redacted by Admin or other User Role with proper permissions
- Modular DB Migrations :
- Each plugin/component has a `migrations` folder
2026-09-20 05:13:41 +00:00
- The migrations folder resides inside the plugin directory (`plugins/blog/migrations/{migration files}`)
2026-09-09 17:07:55 +00:00
- CMS scans and runs them automatically on install
- Plugin table names should be in the form of {plugin_name}_{table_name}
- Example - Blog_Posts, Blog_Post_Versions
- Plugin Upload :
- ZIP directory upload (Admin selects "Plugin" or "Theme" type)
- Uninstall/Remove : Available only when Plugin/Module is Deactivated.
- Runs reverse migrations (regress DB changes).
- Deletes the plugin directory from `plugins/`.
- Configuration Settings :
- If Plugin requires configuration, can add a section to Configuration Settings in Admin
- Admin Section :
- Plugin can add it's own section to Admin section :
- Example : Blog Plugin adds Posts so Author can create Blog Posts on site.
- Front-end :
- Plugin can include it's own template partials for specialized output, or be outputted by installed theme files :
- Example : A calendar Plugin can include it's own calendar.php template file for outputting the calendar
2026-09-20 05:13:41 +00:00
decisions_log_ref : ".decisions/plugin_architecture_decisions.yaml"
2026-09-09 17:07:55 +00:00
open_questions : [ ]
themes_architecture :
description : >
The theme architecture provides a flexible way to customize the look and feel of
SiteWeaverCMS. Themes can be easily installed, switched, and customized through
the admin interface.
notes :
- Themes :
- Default theme (provided by default) supports Light/Dark switching, and set to System by default
- Admin theme supports Light/Dark switching, and set to System by default
2026-09-20 05:13:41 +00:00
- Laravel Blade templates (accessible for non-UI devs)
2026-09-09 17:07:55 +00:00
- Uses `$this->themeUrl($asset)` to produce a url to the asset in the theme directory
- Uses `$this->adminThemeUrl($asset)` to produce a url to the asset in the admin theme directory
- exposes `$theme` variable to templates :
- $theme variable contains all the theme meta data (with lowercase variables) with exception of hooks :
- the hooks section uses it's own set of methods, listed below (in Frontend Theme directory structure and Admin Theme directory structure)
- "Example: `$theme->title` contains the title of the theme as specified in theme.md Title"
- "Example: `$theme->author` contains the author of the theme as specified in theme.md Author"
- Theme Metadata via `theme.md` (frontend theme) :
- Title
- Author
- Description
- Date
- Version
- Core Version
- Hooks :
- css :
- ... list of css files to add to theme head (path relative to theme root)
- js :
- header :
- ... list of js files to add to theme head (path relative to theme root)
- footer :
- ... list of js files to add to theme footer (path relative to theme root)
- "{template_name}" :
- ... list of js files to add at the top of a template before rendering it (path relative to theme root)
- Theme Metadata via `admin.theme.md` (admin theme) :
- same metadata as frontend theme, only file name is `admin.theme.md` instead of `theme.md`
- Simplified architecture (avoiding WordPress-style convolution)
- Theme Upload :
- ZIP directory upload via Admin section
- Uninstall/Remove :
- Available only for themes not currently in use.
- Deletes the theme directory from `themes/`.
- Triggered via button on Theme List page (Delete button per list item)
- Note : Themes should not make DB changes on install.
- Frontend Theme directory folder structure :
- theme.md (required)
- layout.php (required)
- partials :
- optional, unless layout.php uses `$theme->partial()`
- ... (any files called by layout.php using the `$theme->partial($path)` method)
- subdirectories allowed in $path
- "`$theme->partial('footer/contact-block')` would render `partials/footer/contact-block.php`"
- assets :
- css :
- $theme->css() (outputs all the link tags for the list of css files in theme.md hooks/css)
- js :
- $theme->js('header') (outputs all the script tags for the list of js files listed in theme.md hooks/js/header)
- $theme->js('footer') (outputs all the script tags for the list of js files listed in theme.md hooks/js/footer)
- $theme->js($template_name) (outputs all the script tags for the list of js files listed in theme.md hooks/js/{template_name})
- images :
- $theme->imageUrl($image_name) (outputs the url to the image (not the whole img tag, only the url). Subdirectory path allowed)
- Admin Theme directory structure :
- same directory structure as frontend theme, except :
- partials are rendered using `$theme->adminPartial()`, not `$theme->partial()`
- css is output via `$theme->adminCss()`, not `$theme->css()`
- js is output via `$theme->adminJs($section)`, not `$theme->js($section)`
- Admin section Frontend Theme Page :
- Lists each theme (its name and screenshot.jpg) :
- If Active :
- shows "Active"
- cannot be deleted (no delete button shown)
- If not Active :
- Shows "Activate" button and "Delete" button
- When Activate button is clicked, Deactivates previous Active theme, Activates current Theme
- Only one Admin Theme may be active at a time.
- Clicking on Theme name sends you to Admin Theme Detail Page for that Theme
- Frontend Theme Detail Page :
- Lists Theme metadata details (theme.md)
- If Active, shows "Active", else provides Activate button
- Admin section Admin Theme Page :
- Lists each theme (its name and screenshot.jpg) :
- If Active :
- shows "Active"
- cannot be deleted (no delete button shown)
- If not Active :
- Shows "Activate" button and "Delete" button
- When Activate button is clicked, Deactivates previous Active theme, Activates current Theme
- Only one Admin Theme may be active at a time.
- Clicking on Theme name sends you to Admin Theme Detail Page for that Theme
- Admin Theme Detail Page :
- Lists Theme metadata details (admin.theme.md)
- If Active, shows "Active", else provides Activate button
- Theme Editor File Versioning :
- Each save made through the in-place Theme Editor creates a new version of
the saved file, so an accidental bad edit can be reverted without needing
the full Site Restore feature.
2026-09-20 05:13:41 +00:00
decisions_log_ref : ".decisions/themes_architecture_decisions.yaml"
2026-09-09 17:07:55 +00:00
open_questions : [ ]
components :
description : >
SiteWeaverCMS consists of several key components that work together to provide
a complete content management solution. These include the core system, plugins,
themes, and various service components.
2026-09-20 05:13:41 +00:00
decisions_log_ref : ".decisions/components_decisions.yaml"
2026-09-09 17:07:55 +00:00
open_questions : [ ]
notes : [ ]
data_model :
description : >
SiteWeaverCMS uses a relational database model with support for multiple
storage engines. The data model is designed to be extensible through plugins
and themes.
notes :
- Site Settings :
- Site Title/Description
- Global SEO Settings
2026-09-20 05:13:41 +00:00
- Locales table uses `is_supported` to indicate supported locales, and `is_default` marks the default locale.
2026-09-09 17:07:55 +00:00
- Blog Settings :
- Blog Title
- Inherits locales support from Site Settings
- Configurable Blog "Home" URL segment (Admin/Editor can set the entry point for the blog plugin)
- Database Tables :
- Site_Settings :
2026-09-20 05:13:41 +00:00
- fields :
- id : int, not null
- name : string(255), not null
- setting : string(255), null
- created_at : datetime, not null, default=now()
- created_by : int, not null
- updated_at : datetime, null
- updated_by : int, null
- indexes :
- primary_key : id
- foreign_key : created_by (Users.id)
- foreign_key : updated_by (Users.id)
- index : name
- Locales :
- fields :
- id : int, not null
- code : string(7), not null
- name : string(100), not null
- direction : enum('ltr','rtl'), not null, default='ltr'
- is_supported : bool, not null, default=false
- is_default : bool, not null, default=false
- created_at : datetime, not null, default=now()
- updated_at : datetime, null
- indexes :
- primary_key : id
- unique : code
- unique : (CASE WHEN is_default = true THEN 1 ELSE NULL END)
- index : name
2026-09-09 17:07:55 +00:00
- Users :
2026-09-20 05:13:41 +00:00
- fields :
- id : int, not null
- username : string(255), not null
- password : string(255), not null
- f_name : string(255), not null
- l_name : string(255), null
- email : string(255), not null
- avatar : string(255), null, comment="user account image, uses default if null"
- failed_login_attempts : int, not null, default=0
- locked_until : datetime, null
- created_at : datetime, not null, default=now()
- created_by : int, not null
- updated_at : datetime, null
- updated_by : int, null
- indexes :
- primary_key : id
- foreign_key : created_by (Users.id)
- foreign_key : updated_by (Users.id)
- unique : username
- unique : email
2026-09-09 17:07:55 +00:00
- User_Totp :
2026-09-20 05:13:41 +00:00
- fields :
- id : int, not null
- user_id : int, not null
- secret : string(32), not null
- enabled : boolean, not null, default=true
- created_at : datetime, not null
- updated_at : datetime, null
- indexes :
- primary_key : id
- foreign_key : user_id (Users.id)
2026-09-09 17:07:55 +00:00
- User_password_reset :
2026-09-20 05:13:41 +00:00
- fields :
- id : int, not null
- user_id : int, not null
- token : string(42), not null, unique
- expires_at : datetime, not null
- created_at : datetime, not null
- indexes :
- primary_key : id
- foreign_key : user_id (Users.id)
2026-09-09 17:07:55 +00:00
- User_email_verification :
2026-09-20 05:13:41 +00:00
- fields :
- id : int, not null
- user_id : int, not null
- token : string(42), not null
- expires_at : datetime, not null
- created_at : datetime, not null
- indexes :
- primary_key : id
- foreign_key : user_id (Users.id)
- unique : token
- Roles (Stores each logical role (e.g., Developer, Admin, Editor, Viewer)) :
- fields :
- id : int, not null
- name : string(255), not null
- description : string(255), null
- created_at : datetime, not null, default=now()
- created_by : int, not null
- updated_at : datetime, null
- updated_by : int, null
- indexes :
- primary_key : id
- unique : name
- foreign_key : created_by (Users.id)
- foreign_key : updated_by (Users.id)
2026-09-09 17:07:55 +00:00
- Permissions (Stores each distinct permission (e.g., read:document, delete:user)) :
2026-09-20 05:13:41 +00:00
- fields :
- id : int, not null
- name : string(255), not null
- type : string(255), null
- action : string(255), null
- description : string(255), null
- created_at : datetime, not null, default=now()
- created_by : int, not null
- updated_at : datetime, null
- updated_by : int, null
- indexes :
- primary_key : id
- unique : name
- foreign_key : created_by (Users.id)
- foreign_key : updated_by (Users.id)
2026-09-09 17:07:55 +00:00
- Role_Permission (Junction table that maps many roles to many permissions) :
2026-09-20 05:13:41 +00:00
- fields :
- role_id : int, not null
- permission_id : int, not null
- created_at : datetime, not null, default=now()
- created_by : int, not null
- updated_at : datetime, null
- updated_by : int, null
- indexes :
- primary_key : (role_id, permission_id)
- foreign_key : role_id (Roles.id)
- foreign_key : permission_id (Permissions.id)
- foreign_key : created_by (Users.id)
- foreign_key : updated_by (Users.id)
2026-09-09 17:07:55 +00:00
- User_Role (Junction table that maps many users to many roles) :
2026-09-20 05:13:41 +00:00
- fields :
- user_id : int, not null
- role_id : int, not null
- created_at : datetime, not null, default=now()
- created_by : int, not null
- updated_at : datetime, null
- updated_by : int, null
- indexes :
- primary_key : (user_id, role_id)
- foreign key : user_id (Users.id)
- foreign key : role_id (Roles.id)
- foreign key : created_by (Users.id)
- foreign key : updated_by (Users.id)
2026-09-09 17:07:55 +00:00
- Pages :
2026-09-20 05:13:41 +00:00
- fields :
- id : int, not null
- slug : string(255), comment="human‑ readable URL part"
- title : string(255)
- created_at : datetime, not null, default=now()
- created_by : int, not null
- updated_at : datetime, null
- updated_by : int, null
- published_at : datetime, null - (set once on first publish (first version publish), never updated)
- indexes :
- primary_key : id
- unique : slug
- foreign key : created_by (Users.id)
- foreign key : updated_by (Users.id)
2026-09-09 17:07:55 +00:00
- Page_Versions :
2026-09-20 05:13:41 +00:00
- fields :
- id : int, not null
- page_id : int, not null
- version_number : int, not null - (incremental per page per locale, starts at 1)
- locale : string(7), not null
- content : text, not null, comment="the full HTML or templating markup"
- meta_json : json/text, null, comment="extra fields like seo_title, meta_description, tags"
- created_at : datetime, not null
- created_by : int, not null
- is_published : bool, not null, default=false
- published_at : datetime, null
- indexes :
- primary_key : id
- foreign_key : page_id (Pages.id)
- foreign key : created_by (Users.id)
2026-09-09 17:07:55 +00:00
- Blog_Settings (Blog plugin) :
2026-09-20 05:13:41 +00:00
- fields :
- id : int, not null
- name : string(255), not null
- setting : string(255), null
- created_at : datetime, not null, default=now()
- created_by : int, not null
- updated_at : datetime, null
- updated_by : int, null
- indexes :
- primary_key : id
- unique : name
- foreign key : created_by (Users.id)
- foreign key : updated_by (Users.id)
2026-09-09 17:07:55 +00:00
- Blog_Posts (Blog plugin) :
2026-09-20 05:13:41 +00:00
- fields :
- id : int, not null
- slug : string(255), not null, comment="human‑ readable URL part"
- title : string(255), not null
- created_at : datetime, not null, default=now()
- created_by : int, not null
- updated_at : datetime, null
- updated_by : int, null
- published_at : datetime, null - (set once on first publish (first version publish), never updated)
- indexes :
- primary_key : id
- unique : slug
- foreign key : created_by (Users.id)
- foreign key : updated_by (Users.id)
2026-09-09 17:07:55 +00:00
- Blog_Post_Versions (Blog plugin) :
2026-09-20 05:13:41 +00:00
- fields :
- id : int, not null
- post_id : int, not null
- version_number : int, not null (incremental per post_id per locale, starts at 1)
- locale : string(7), not null
- extract : text, null (if null, extract is pulled from content)
- content : text, not null, comment="the full HTML or templating markup"
- meta_json : json/text, null comment="extra fields like seo_title, meta_description, tags"
- created_at : datetime, not null
- created_by : int, not null
- is_published : bool, not null, default=false
- published_at : datetime, null
- indexes :
- primary_key : id
- foreign_key : post_id (Blog_Posts.id)
- foreign_key : created_by (Users.id)
2026-09-09 17:07:55 +00:00
- Blog_Categories (Blog plugin) :
2026-09-20 05:13:41 +00:00
- fields :
- id : int, not null
- name : string(255), not null
- slug : string(255), not null
- description : text, null
- indexes :
- primary_key : id
- unique : name
- unique : slug
2026-09-09 17:07:55 +00:00
- Blog_Tags (Blog plugin) :
2026-09-20 05:13:41 +00:00
- fields :
- id : int, not null
- name : string(255), not null
- description : text, null
- indexes :
- primary_key : id
- unique : name
2026-09-09 17:07:55 +00:00
- Blog_Post_Categories (Blog plugin) :
2026-09-20 05:13:41 +00:00
- fields :
- post_id : int, not null
- category_id : int, not null
- created_at : datetime, not null, default=now()
- created_by : int, not null
- indexes :
- primary_key : (post_id, category_id)
- foreign_key : post_id (Blog_Posts.id)
- foreign_key : category_id (Blog_Categories.id)
- foreign key : created_by (Users.id)
2026-09-09 17:07:55 +00:00
- Blog_Post_Tags (Blog plugin) :
2026-09-20 05:13:41 +00:00
- fields :
- post_id : int, not null
- tag_id : int, not null
- created_at : datetime, not null, default=now()
- created_by : int, not null
- indexes :
- primary_key : (post_id, tag_id)
- foreign_key : post_id (Blog_Posts.id)
- foreign_key : tag_id (Blog_Tags.id)
- foreign key : created_by (Users.id)
2026-09-09 17:07:55 +00:00
- Blog_Comments :
2026-09-20 05:13:41 +00:00
- fields :
- id : int, not null
- post_id : int, not null
- user_id : int, not null
- content : text, not null
- likely_spam : boolean, not null
- created_at : datetime, not null
- updated_at : datetime, null
- updated_by : int, null
- indexes :
- primary_key : id
- foreign_key : post_id (Blog_Posts.id)
- foreign_key : user_id (Users.id)
- foreign key : updated_by (Users.id)
2026-09-09 17:07:55 +00:00
- Media :
2026-09-20 05:13:41 +00:00
- fields :
- id : int, not null
- filename : string(255), not null
- path : string(255), not null
- storage_backend : string(255), not null
- mime_type : string(75), not null
- height : int, not null
- width : int, not null
- created_at : datetime, not null, default=now()
- created_by : int, not null
- updated_at : datetime, null
- updated_by : int, null
- indexes :
- primary_key : id
- foreign key : created_by (Users.id)
- foreign key : updated_by (Users.id)
2026-09-09 17:07:55 +00:00
- Media_Metadata :
2026-09-20 05:13:41 +00:00
- fields :
- id : int, not null
- media_id : int, not null
- name : string(255), not null
- alt : text, null, default=null
- locale : string(3), not null
- created_at : datetime, not null, default=now()
- created_by : int, not null
- updated_at : datetime, null
- updated_by : int, null
- indexes :
- primary_key : id
- foreign_key : media_id (Media.id)
- foreign key : created_by (Users.id)
- foreign key : updated_by (Users.id)
2026-09-09 17:07:55 +00:00
- Plugins :
2026-09-20 05:13:41 +00:00
- fields :
- id : int, not null
- name : string(255), not null
- version : string(10), not null
- core_version : string(10), not null
- status : enum('Active','Deactivated','Error'), not null, default='Deactivated'
- created_at : datetime, not null, default=now()
- created_by : int, not null
- updated_at : datetime, null
- updated_by : int, null
- indexes :
- primary_key : id
- foreign key : created_by (Users.id)
- foreign key : updated_by (Users.id)
2026-09-09 17:07:55 +00:00
- Frontend_Themes :
2026-09-20 05:13:41 +00:00
- fields :
- id : int, not null
- name : string(255), not null
- enabled : boolean, not null, default=false, comment="Only one may be enabled at a time. Currently controlled by app code, not DB constraints"
- created_at : datetime, not null, default=now()
- created_by : int, not null
- updated_at : datetime, null
- updated_by : int, null
- indexes :
- primary_key : id
- foreign key : created_by (Users.id)
- foreign key : updated_by (Users.id)
2026-09-09 17:07:55 +00:00
- Admin_Themes :
2026-09-20 05:13:41 +00:00
- fields :
- id : int, not null
- name : string(255), not null
- enabled : boolean, not null, default=false, comment="Only one may be enabled at a time. Currently controlled by app code, not DB constraints"
- created_at : datetime, not null, default=now()
- created_by : int, not null
- updated_at : datetime, null
- updated_by : int, null
- indexes :
- primary_key : id
- foreign key : created_by (Users.id)
- foreign key : updated_by (Users.id)
- Backups :
- fields :
- id : int, not null
- filename : string(255), not null
- size : bigint, not null
- created_at : datetime, not null, default=now()
- indexes :
- primary_key : id
- index : filename
2026-09-09 17:07:55 +00:00
example_seed_data_for_roles : >
INSERT INTO Roles (name, description) VALUES
2026-09-20 05:13:41 +00:00
('Developer', 'Full access to all CMS features and settings. Can create Admins'),
2026-09-09 17:07:55 +00:00
('Admin', 'Full access to all CMS features and settings.'),
('Editor', 'Can create, edit, and delete content, but cannot change site settings.'),
('Author', 'Can create and edit their own content; can view drafts.'),
('User', 'Basic frontend read‑ only access to published content. No backend admin access. Can comment on Blog Posts');
-- 2 . Insert a set of permissions that are common to most CMSes
INSERT INTO Permissions (name, type, action, description) VALUES
('manage:site', NULL, 'configure', 'Change site‑ wide settings (e.g., logo, navigation).'),
('publish:page', 'page', 'publish', 'Publish a page to the public site.'),
('unpublish:page', 'page', 'unpublish', 'Remove a page from public view.'),
('edit:page', 'page', 'edit', 'Modify an existing page.'),
('delete:page', 'page', 'delete', 'Permanently delete a page.'),
('manage:user', NULL, 'admin', 'Create, edit, disable, or delete user accounts.'),
('view:draft', 'draft', 'view', 'Read unpublished drafts.'),
('edit:draft', 'draft', 'edit', 'Edit an unpublished draft.'),
('publish:draft', 'draft', 'publish', 'Publish a draft to the public site.');
-- 3 . Assign permissions to the default roles (many‑ to‑ many via Role_Permission)
-- The composite PK (role_id, permission_id) guarantees no duplicate rows.
INSERT INTO Role_Permission (role_id, permission_id)
SELECT r.id, p.id
FROM Roles r, Permissions p
WHERE r.name = 'Admin' AND p.name IN (SELECT name from Permissions);
INSERT INTO Role_Permission (role_id, permission_id)
SELECT r.id, p.id
FROM Roles r, Permissions p
WHERE r.name = 'Editor' AND p.name IN ('publish:page','unpublish:page', 'edit:page','delete:page', 'view:draft','edit:draft');
INSERT INTO Role_Permission (role_id, permission_id)
SELECT r.id, p.id
FROM Roles r, Permissions p
WHERE r.name = 'Author' AND p.name IN ('publish:page','edit:page', 'edit:draft','publish:draft');
2026-09-20 05:13:41 +00:00
seed_data_for_locales : >
INSERT INTO Locales (code, name, direction) VALUES
("en","English","ltr"), ("sq","Albanian","ltr"), ("ar","Arabic","rtl"), ("az","Azerbaijani","ltr"), ("eu","Basque","ltr"), ("bn","Bengali","ltr"),
("bg","Bulgarian","ltr"), ("ca","Catalan","ltr"), ("zh-Hans","Chinese","ltr"), ("zh-Hant","Chinese (traditional)","ltr"),
("cs","Czech","ltr"), ("da","Danish","ltr"), ("nl","Dutch","ltr"), ("eo","Esperanto","ltr"), ("et","Estonian","ltr"), ("fi","Finnish","ltr"),
("fr","French","ltr"), ("gl","Galician","ltr"), ("de","German","ltr"), ("el","Greek","ltr"), ("he","Hebrew","rtl"), ("hi","Hindi","ltr"),
("hu","Hungarian","ltr"), ("id","Indonesian","ltr"), ("ga","Irish","ltr"), ("it","Italian","ltr"), ("ja","Japanese","ltr"), ("ko","Korean","ltr"),
("ky","Kyrgyz","ltr"), ("lv","Latvian","ltr"), ("lt","Lithuanian","ltr"), ("ms","Malay","ltr"), ("nb","Norwegian","ltr"), ("fa","Persian","rtl"),
("pl","Polish","ltr"), ("pt","Portuguese","ltr"), ("pt-BR","Portuguese (Brazil)","ltr"), ("ro","Romanian","ltr"), ("ru","Russian","ltr"),
("sr","Serbian","ltr"), ("sk","Slovak","ltr"), ("sl","Slovenian","ltr"), ("es","Spanish","ltr"), ("sw","Swahili","ltr"), ("sv","Swedish","ltr"),
("tl","Tagalog","ltr"), ("th","Thai","ltr"), ("tr","Turkish","ltr"), ("uk","Ukrainian","ltr"), ("ur","Urdu","rtl"), ("vi","Vietnamese","ltr");
decisions_log_ref : ".decisions/data_model_decisions.yaml"
2026-09-09 17:07:55 +00:00
open_questions : [ ]
deployment :
description : >
SiteWeaverCMS is designed for self-hosted deployment with support for various
web server configurations and environments.
2026-09-20 05:13:41 +00:00
decisions_log_ref : ".decisions/deployment_decisions.yaml"
2026-09-09 17:07:55 +00:00
open_questions : [ ]
notes : [ ]
security :
description : >
2026-09-20 05:13:41 +00:00
SiteWeaverCMS implements security best practices including input validation, csrf on every form,
form data cleansing (no <script> tags in form data submission), standard sql preparation (Eloquent does this by
default, I believe), authentication, and secure coding practices.
decisions_log_ref : ".decisions/security_decisions.yaml"
2026-09-09 17:07:55 +00:00
open_questions : [ ]
notes : [ ]
performance :
description : >
SiteWeaverCMS is optimized for performance with caching strategies, lazy loading,
and efficient database queries.
2026-09-20 05:13:41 +00:00
decisions_log_ref : ".decisions/performance_decisions.yaml"
2026-09-09 17:07:55 +00:00
open_questions : [ ]
notes : [ ]
secret_management :
notes :
- The project will use a self‑ hosted Infisical instance to store all runtime secrets.
- If Infisical is unavailable, the app should throw an exception, and tell the viewer to contact the Website Administrator.
2026-09-20 05:13:41 +00:00
- Infisical credentials are injected into the application via environment variables set in Nginx.
- The remaining environment variables will be obtained from Infisical
- The system should not rely on .env, except when in dev environment.
2026-09-09 17:07:55 +00:00
- During bootstrap, the app connects to Infisical, pulls all secrets into a local array, and passes it to a new Config instance.
2026-09-20 05:13:41 +00:00
- The Config class stores these values internally; subsequent code accesses them via the config service provided by the DI container.
2026-09-09 17:07:55 +00:00
nginx_env_vars :
- INFISICAL_URL
- INFISICAL_ENV ("dev","staging","production")
- INFISICAL_PROJECT_ID
- INFISICAL_SECRET_ID
- INFISICAL_MACHINE_ID
composer_require :
- composer require infisical/php-sdk
php_sdk :
- https://infisical.com/docs/sdks/languages/php
tech_stack :
language : PHP 8.4+
2026-09-20 05:13:41 +00:00
backend_framework : Laravel 13.x
2026-09-09 17:07:55 +00:00
backend_static_analysis :
- PHPStan (for general code quality)
- Psalm (for code security)
backend_code_style_tool : PHP CS Fixer
css_framework : Fomantic UI
js_framework : Vue (using Vue Components)
package_management : Composer
database :
engines :
- MySQL/MariaDB
2026-09-20 05:13:41 +00:00
migration :
- Laravel Eloquent migrations
testing_framework : Pest
2026-09-09 17:07:55 +00:00
notes : [ ]
2026-09-20 05:13:41 +00:00
decisions_log_ref : ".decisions/tech_stack_decisions.yaml"
2026-09-09 17:07:55 +00:00
open_questions : [ ]
suggestions : [ ]
features_overview :
# High-level list of features/capabilities the app will provide.
features :
- Site Backup
- Site Restore
- Page Builder Core
- Search Functionality
- Comments System
- Media Manager
- Blog plugin
- i18n
- Theme/Plugin Management
- User Management
- Role Based Auth Management
notes :
- Site Backup :
- File and Database backup :
- Not automatic in v1
- Not regular schedule in v1
- Via action triggered in Admin section
- Does `sqldump` (MySQL) or equiv for database, adds to zip archive of CMS folder, offers download of zip archive
- Site Restore :
- Restoring the site from a previous backup
- Page Builder Core :
- The CMS itself is a robust Page Builder
- Blog functionality is a plugin/module interacting with the Page Builder core, Media Manager, and Navigation
- Search Functionality :
- Full-text search for posts and pages
- Category-based filtering (Blog Post Search by tags)
- Faceted Search (Blog Section Only, Left Column, Amazon/eBay style)
- Text search input
- Category/Tag checkboxes
- Date range filters
- Dynamic filtering results
- Comments System :
2026-09-20 05:13:41 +00:00
- Part of the Blog plugin, belongs in Blog plugin directory
2026-09-09 17:07:55 +00:00
- Only in Blog posts, not part of Pages
- Author can turn off comments for each blog post
- Author can delete User comments
- Spam protection - Akismet integration + honeypot + rate-limiting
2026-09-20 05:13:41 +00:00
decisions_log_ref : ".decisions/features_overview_decisions.yaml"
2026-09-09 17:07:55 +00:00
open_questions : [ ]
suggestions : [ ]
# Per-domain architecture/requirements sections go here, e.g.:
# auth_requirements:
# Adapt the exact set of sections to the project as it takes shape.
# (A separate backend_architecture section was considered and intentionally omitted —
# see the decisions_log under `architecture` below. A dedicated frontend_architecture
# section was added instead — see the decisions_log under `architecture` and the
# `frontend_architecture` section itself, below.)
frontend_architecture :
notes :
- Vue is used exclusively via its Components feature (not as a full SPA framework),
per the `js_framework` decision in `tech_stack`; individual Vue components are
mounted onto elements within server-rendered PHP theme templates
(`layout.php`/partials), not a client-side-routed app shell.
2026-09-20 05:13:41 +00:00
- Fomantic UI supplies the semantic CSS class vocabulary for markup rendered by Laravel Blade templates (`theme.md`/`layout.php`/partials, per
2026-09-09 17:07:55 +00:00
`themes_architecture`); Vue components are expected to reuse Fomantic UI classes
rather than introduce a competing utility-class system.
- Compiled/bundled CSS and JS output lands in `public/build` (per
`directory_structure`); each theme's own `assets/css` and `assets/js` (and any
shared/core frontend source) are the inputs to that build step.
2026-09-20 05:13:41 +00:00
- Laravel Blade templates handle server-side rendering of full pages; Vue
2026-09-09 17:07:55 +00:00
Components layer on top only for interactive widgets (e.g. the Theme/Plugin
Editor's split-pane code editor, the Media Manager's Focal Point Selector, the
Faceted Search filter panel).
- Vite is the build tool that bundles the Vue Single-File Components into the
`public/build` output.
- Vue Components are authored as Single-File Components (`.vue` files) living
under each theme's `assets/js/components` directory (see `directory_structure`).
- Each interactive widget gets its own independent Vue instance (no single global
app instance mounting every widget on a page).
- A mounted component is referenced in server-rendered markup as a custom HTML
element tag matching the component name (e.g. `<AlertBox message="Something went
wrong">`), with data passed in as HTML attributes that Vue exposes to the
component as props.
2026-09-20 05:13:41 +00:00
decisions_log_ref : ".decisions/frontend_architecture_decisions.yaml"
2026-09-09 17:07:55 +00:00
open_questions : [ ]
directory_structure :
notes :
- Secrets are injected via environment variables set by Nginx from a self‑ hosted Infisical instance
- Example setting environment variables in nginx config file
- >
```
2026-09-20 05:13:41 +00:00
fastcgi_param INFISICAL_URL "https://your.infisical.instance";
fastcgi_param INFISICAL_TOKEN "YOUR_INFISICAL_TOKEN";
fastcgi_param INFISICAL_SECRET_ID "YOUR_PROJECT_OR_FOLDER_ID";
...
2026-09-09 17:07:55 +00:00
```
2026-09-20 05:13:41 +00:00
- app :
- Laravel standard application code :
- Controllers
- Models
- Services
- Providers
- etc.
2026-09-09 17:07:55 +00:00
- bootstrap
2026-09-20 05:13:41 +00:00
- config : (PHP configuration files; Infisical connection environment secrets are injected via Nginx; the remaining environment variables come from the self‑ hosted Infisical instance)
2026-09-09 17:07:55 +00:00
- database :
2026-09-20 05:13:41 +00:00
- migrations : (Laravel Eloquent migrations for core tables - users, roles, permissions, pages, posts, settings, etc.)
- seeds : (Laravel seeders for default data - default roles, default admin theme, default frontend theme, etc.)
2026-09-09 17:07:55 +00:00
- services :
2026-09-20 05:13:41 +00:00
- providers : (Service Provider classes that register/bootstrap functionality with the Laravel app, per the Service Provider glossary term)
- (actual service implementation classes live directly under app/Services, e.g. TranslationService.php, BackupService.php, StorageService.php)
2026-09-09 17:07:55 +00:00
- public : (web server root, contains front-controller)
- build (compiled/bundled CSS and JS output produced by the front-end build step)
- site :
- admin_themes :
- SiteWeaverAdmin2026 : (same internal structure as a Frontend Theme below, using admin.theme.md instead of theme.md)
- plugins :
- blog :
- plugin.md (required; plugin metadata = Title, Author, Description, Date, Version, Core Version)
2026-09-20 05:13:41 +00:00
- migrations (this plugin's own Laravel Eloquent migrations; scanned and run automatically on install, per Modular DB Migrations)
2026-09-09 17:07:55 +00:00
- (Service Provider class registering the plugin with the core app)
- admin (the plugin's own Admin section pages, e.g. Posts CRUD for the Blog Post Author)
- partials (the plugin's own template partials for front-end output, e.g. post.php, postlist.php)
- themes :
- SiteWeaver2026 :
- theme.md (required; theme metadata - Title, Author, Description, Date, Version, Hooks)
- layout.php (required)
- screenshot.jpg
- partials : (optional; rendered via $theme->partial($path), subdirectories allowed)
- assets :
- css :
- js :
- components (Vue Single-File Components (`.vue`), per `frontend_architecture`)
- images :
- storage (outside public/, not directly web-accessible; served through a controller/route instead) :
- media : (uploaded files when using the Local Disk storage backend)
- cache : (JIT-generated image variants, and Preview/Publish Cache Warming output for Pages/Posts)
2026-09-20 05:13:41 +00:00
- backups : (all system generated backup files, triggered by Site Backup "Create Backup" button)
2026-09-09 17:07:55 +00:00
- tests :
- behavioral :
- feature :
- integration :
- unit :
2026-09-20 05:13:41 +00:00
decisions_log_ref : ".decisions/directory_structure_decisions.yaml"
2026-09-09 17:07:55 +00:00
open_questions : [ ]
suggestions :
- Add a reference to the self-hosted Infisical instance and document the required environment variables for Nginx.
media_manager_architecture :
notes :
- Media Manager :
- Focal Point Selector : Picking focus ensures art direction
- Just-In-Time (JIT) Generation (Glide)
- Preview Cache Warming + Automated Cache Warming on Publish
- Orphaned Media Watcher : Identifies unreferenced files and cached artifacts
- Folder organization
- Storage Abstractions : Support for Local Disk, S3, Nextcloud, and Linode Object-Storage (S3-compatible)
- Define a Storage Interface : <
(`StorageInterface::get($path), put($path,$data), delete($path)`) and have each backend implement it.
The rest of the CMS (media upload, JIT generation) talks only to that interface.
This makes swapping providers painless and lets you add, e.g., Azure Blob Storage later.
- Allowed file-extensions :
- Images :
- .jpg :
- MIME-type : image/jpeg
- Max Upload Size : 10 MB
- .jpeg :
- MIME-type : image/jpeg
- Max Upload Size : 10 MB
- .png :
- MIME-type : image/png
- Max Upload Size : 10 MB
- .gif :
- MIME-type : image/gif
- Max Upload Size : 10 MB
- .bmp :
- MIME-type : image/bmp
- Max Upload Size : 10 MB
- .ico :
- MIME-type : image/vnd.microsoft.icon
- MIME-type : image/x-icon
- Max Upload Size : 10 MB
- .svg :
- MIME-type : image/svg+xml
- Max Upload Size : 10 MB
- Videos :
- .mp4 :
- MIME-type : video/mp4
- Max Upload Size : 100 MB
- .mov :
- MIME-type : video/quicktime
- Max Upload Size : 100 MB
- .avi :
- MIME-type : video/x-msvideo
- Max Upload Size : 100 MB
- .wmv :
- MIME-type : video/x-ms-asf
- MIME-type : video/x-ms-wmv
- Max Upload Size : 100 MB
- Upload validation :
- Check MIME type against allowed list.
- Return clear error if max size exceeded or unsupported MIME type.
2026-09-20 05:13:41 +00:00
decisions_log_ref : ".decisions/media_manager_architecture_decisions.yaml"
2026-09-09 17:07:55 +00:00
open_questions : [ ]
suggestions : [ ]
auth_requirements :
notes :
- User Management :
- Roles & Permissions :
2026-09-20 05:13:41 +00:00
- Developer (created by default)
2026-09-09 17:07:55 +00:00
- Admin (created by default)
- Editor (created by default)
- Author (created by default)
- User (created by default)
2026-09-20 05:13:41 +00:00
- other roles can be created by Admin, Developer, or those with permission granted by Role
- All roles other than Admin or Developer can be created/edited/deleted by Admin or Developer (including Editor, Author, User)
2026-09-09 17:07:55 +00:00
- User Management CRUD in Backend Admin Section
2026-09-20 05:13:41 +00:00
- Developer Protection : Only one Developer account allowed; this will be your account. Developer role/account cannot be edited or deleted by anyone other than Developer
- Admin Protection : Admin role/account cannot be edited or deleted by anyone other than Developer or Admin
- Role Extensibility : All other roles can be modified, and new custom roles can be added. Developers can create Admin accounts; Admins cannot create additional Admin accounts.
2026-09-09 17:07:55 +00:00
- Granular Permissions : CRUD-level control (View, Edit, Delete) per resource (Page, Plugin, Theme, Blog Post, User)
2026-09-20 05:13:41 +00:00
- Profile Management : Backend users (Developer, Admin, Editor, Author) can update their own profile information (Name, Email, Password).
2026-09-09 17:07:55 +00:00
- User role : Public-facing site viewers who can comment on blog posts, but cannot access the admin section
- Blog Post Author Permissions (Hard-coded/Default) :
- Authors can view/edit only their own blog posts in the admin backend by default.
- Access to other authors' posts is restricted unless explicitly permitted by Role.
- Deleting their own "Published" blog posts is a granular permission that can be granted or revoked.
- All Published blog posts remain visible to anyone on the front-end.
- Password Reset : >
A logged-in user changes their own password from their Profile page (see
Profile Management above). This covers changing a known password.
- Forgot Password Recovery : >
A logged-out user who has forgotten their password can request a
self-service reset via an emailed, tokenized reset link (valid for 1
hour), which lets them set a new password directly. Successfully
completing a reset also clears/unlocks any active Login Lockout on that
account.
- Login Lockout Policy : >
After 3 consecutive failed login attempts, the account is temporarily
2026-09-20 05:13:41 +00:00
locked for 2 hours; an Admin or Developer can manually override/unlock the lockout at
any time. Only another Admin or the Developer can manually override/unlock an Admin account,
or they must wait for the 2 hour window to expire.
2026-09-09 17:07:55 +00:00
- Email Verification : >
The public-facing User (commenter) role must verify their email after
2026-09-20 05:13:41 +00:00
account creation. Once verified, they can comment on blog posts without
further verification.
2026-09-09 17:07:55 +00:00
- Multiple Admin Accounts : >
2026-09-20 05:13:41 +00:00
A site may have more than one Admin account; those Admins can
2026-09-09 17:07:55 +00:00
create further accounts/roles (Editor, Author, User, etc.) as needed.
2026-09-20 05:13:41 +00:00
decisions_log_ref : ".decisions/auth_requirements_decisions.yaml"
2026-09-09 17:07:55 +00:00
open_questions : [ ]
ui :
notes :
- Theme/Plugin Editor :
- Built-in file editor for theme/plugin files (CSS, JS, PHP, etc.) within the Admin section.
2026-09-20 05:13:41 +00:00
- Only allows editing Themes/Plugins that have been installed. Cannot be used to create new Plugins/Themes.
2026-09-09 17:07:55 +00:00
- Allows small tweaks directly on the server without local development/upload.
- UI Layout :
- '"Theme Editor" header with a dropdown for installed themes in the upper right.'
- Split-pane content area :
- File explorer on the left, code editor on the right.
- File explorer includes a "New File" action button to create new files (Template Partial, CSS, Plugin functionality (PHP), etc.).
- Automatic loading of file content with syntax highlighting upon selection.
- '"Save" and "Cancel" buttons below the split area.'
- Dashboards :
- Every non-User user that logs in is taken to a Dashboard
- Users with only User role are restricted to frontend viewing only (no admin access), and can comment on Blog posts.
- Admin Dashboard :
- Contains a table with number of users broken down by role
- Contains a table with view statistics of top 10 pages/posts (sorted by view count)
- If feasible, shows a table with exception statistics (which exceptions have been thrown and how many times)
- Author Dashboard :
- Contains a table with view statistics of top 10 pages/posts written by them (sorted by view count).
- Contains a table with a list of unpublished pages/posts (Works in Progress)
- Contains a table with the count of comments on each post written by them
- Editor Dashboard :
- To be announced.
2026-09-20 05:13:41 +00:00
- Site Settings :
- Description : A page in the Config section of the backend Admin interface for general site settings.
- Fields :
- Site Title
- Site Description
- Default Locale (dropdown using languages from Locales table)
- Global SEO Settings (meta title, meta description, canonical URL, etc.)
- Persistence : Selecting a Default Locale sets its `is_default` flag to true in the Locales table and clears that flag on any other locale. Exactly one row may have `is_default = true.`
- Languages Page :
- Description : A page in the Config section of the backend Admin interface that manages supported site languages.
- Sections :
- Supported Languages
- Not Supported
- Behavior :
- On first visit, all locales except the site's default appear in "Not Supported".
- Default locale appears in "Supported" and is locked (cannot be moved).
- Locale buttons are flat pill-shaped; clicking moves button between sections.
- Persistence : The UI automatically updates the `Locales` table entries (`is_supported`, `is_default`) to reflect the current selection.
- Note : No "Add Locale" button – all supported languages are pre‑ listed by the translation service.
2026-09-09 17:07:55 +00:00
- Media Manager :
- Only accessible by those with permissions to create/edit media entries
- Displays a thumbnail of each stored image.
- When a thumbnail is clicked, a modal appears with full size image at the top, and metadata below (height, width, alt text, filename, etc)
- User Management :
- Only accessible by those with permissions to view/edit users
- Shows a table of each user in the system, with username (link), role(s), date created, date last login, status ("Good", "Locked out", etc)
- When the username link is clicked, a modal shows with username as text, email address as text, roles as checkboxes (all roles listed, checked if user contains role), dates as text, action buttons to Save (if role data has changed), Delete, Unlock.
- Has a button near the top to create new users. When button is clicked, a modal appears with form fields for username (text field), email address (text field), roles (list of checkboxes), a save button and a cancel button at the bottom.
- When modal form is submitted, a new user record is added to the database with a temp password, and an email is sent to the user email address with the temp password.
- On first login, the user is required to change their password.
- Role Management :
- Only accessible by those with permissions to view/edit roles
- Shows a table with a list of roles (links), data created, created by
- When a role link is clicked, takes you to a page with a list of permissions, green checkmarks marking the permissions the role has, red x's marking the permissions the role does not have, and an edit button towards the top.
- When the edit button is clicked, the page turns into a form, with checkboxes (checked if role has permission, unchecked if role does not have permission), a save button, and a cancel button
2026-09-20 05:13:41 +00:00
- Role Creation/Edit Form :
- Fields :
- Role Name : text box
- Permissions : list of checkboxes (green/red icons)
- Validation :
- If checked permissions duplicates an existing role that has exactly these permissions, display warning "Role {role name} already has these exact permissions" and prevent submission.
- Theme / Plugin Manager Pages :
- Frontend Themes List Page :
- Shows list of frontend themes
- Status indicator (Active/Deactivated)
- Activate button if deactivated
- Deactivate button if active
- Uninstall button if deactivated
- Manage button linking to theme’ s config page if present
- Upload New Theme button :
- opens file select dialog for ZIP files
- upon upload :
- server extracts ZIP to site/themes folder
- reads theme.md to get Title
- inserts new record into Frontend_Themes table (enabled = false by default)
- Admin Themes List Page :
- Shows list of admin themes
- Status indicator (Active/Deactivated)
- Activate button if deactivated
- Deactivate button if active
- Uninstall button if deactivated
- Manage button linking to theme’ s config page if present
- Upload New Theme button :
- opens file select dialog for ZIP files
- upon upload :
- server extracts ZIP to site/admin_themes folder
- reads admin.theme.md to get Title
- inserts new record into Admin_Themes table (enabled = false by default)
- Plugins List Page :
- Shows list of plugins
- Status indicator (Active/Deactivated)
- Activate button if deactivated
- Deactivate button if active
- Uninstall button if deactivated
- Manage button linking to plugin’ s config page if present
- Upload New Plugin button :
- opens file select dialog for ZIP files
- upon upload :
- server extracts ZIP to site/plugins folder
- performs security verification (rejects exec functions etc) :
- on failure deletes extracted folder
- on success :
- reads plugin.md for Title, Version, Core Version
- inserts new record into Plugins table (enabled = false by default)
2026-09-09 17:07:55 +00:00
- Page Builder :
- Only accessible by those with permissions to create/edit pages
- A large form to create a page.
- A Page Title text box
- A Checkbox to "Include in Site Navigation"
- A Drop down to choose between custom content or Plugin output.
- If Drop down is custom content, show a WYSIWYG Editor for Page Content (with header buttons (H1-H6), bold, italics, hyperlink, image (include image from media storage), text alignment (applies to entire paragraph (left, middle, right)), switch to Markdown mode, switch to HTML mode)
- If Drop down is Plugin output, show a drop down of active Plugins that have output that can be displayed.
2026-09-20 05:13:41 +00:00
plugin_config_behavior :
description : >
Each plugin may register one or more pages in the top‑ level Config section via its ServiceProvider::register().
default_permissions :
- Developer
- Admin
per_plugin_permissions_schema : >
Plugins can expose custom permissions such as "edit:posts", "create:tags", "config:blog".
These permissions can be assigned to roles, allowing fine‑ grained control over plugin settings.
role_creation_by :
- Developer
- Admin
ordering :
- Site Settings (1)
- Languages (2)
- Alphabetical for all other config pages
visibility_on_status_change :
active : link shown
deactivated : link hidden
decisions_log_ref : ".decisions/ui_decisions.yaml"
2026-09-09 17:07:55 +00:00
open_questions : [ ]
2026-09-20 05:13:41 +00:00
site_settings_page :
fields :
- Site name : text input
- Site logo upload : file upload
- Site keywords : text input
- Site description : multi-line text field
- Site default locale : dropdown (source=Locales table)
2026-09-09 17:07:55 +00:00
project_owner :
notes : >
This repository is maintained by a single developer who builds the core,
themes, and plugins for clients. An Admin account is created for the developer initially;
afterwards an Admin account is provisioned for each client, who can then create additional
user accounts/roles as needed.
project_management :
open_questions : [ ]
team_size : 1
budget_range : $100
deployment_strategy :
- ForgeJo Actions CI/CD
- Dev and Prod Environments (no staging)
testing :
notes :
- Unit Testing
- Feature Testing
- Integration Testing
- Behavior Testing
- Expected Coverage : 95 +% of self-owned code (no need to test code in the vendor directory)
2026-09-20 05:13:41 +00:00
decisions_log_ref : ".decisions/testing_decisions.yaml"
2026-09-09 17:07:55 +00:00
open_questions : [ ]