# CHANGELOG — miniSISKA

All notable changes to this project are documented here.
Format: `[Version] — Date — Summary`

---

## [2.9.0] — 2026-05-26

### Added
- `config/watermark.php` — GD image watermark engine
  - `wm_open_image()` — load JPG/PNG/WebP into GD resource
  - `wm_resolve_source()` — resolve logo or custom asset path
  - `wm_compute_position()` — calculate x/y from position string + padding
  - `wm_blend()` — pixel-level alpha compositing at arbitrary opacity %
  - `wm_apply_image()` — full pipeline: open → scale → position → blend → save `[name]_wm.[ext]`
  - `wm_apply_video()` — Phase 2 placeholder (ffmpeg overlay, not yet active)
  - Non-destructive: `file_path` (original) never modified
- `api/apply_watermark.php` — per-image watermark apply/clear endpoint
  - `action=apply` — stamps watermark, saves `processed_path` to `post_media`
  - `action=clear` — nulls `processed_path`, sets `wm_enabled=0`
  - Validates asset path (must be under `storage/uploads/` or `storage/logos/`)
  - Video type returns informative Phase 2 placeholder error
- `api/preview_watermark.php` — live preview without saving
  - Accepts `post_media_id` or `file_path` as base image source
  - Writes `wm_prev_{bizId}.{ext}` — overwritten each call, no cleanup needed
  - Used by per-image panel in Content Editor
- `api/upload_watermark.php` — brand watermark asset upload
  - Accepts PNG/WebP/JPG up to 2 MB
  - Saves to `storage/logos/wm_[uniqid].[ext]`
- `database/migrate_watermark.sql` — safe idempotent migration
  - `business_profiles`: 7 new columns (`wm_enabled`, `wm_source`, `wm_path`, `wm_position`, `wm_opacity`, `wm_size_pct`, `wm_padding`)
  - `post_media`: 7 new columns (`processed_path`, `wm_enabled`, `wm_asset_path`, `wm_position`, `wm_opacity`, `wm_size`, `wm_margin`)
  - All use `ADD COLUMN IF NOT EXISTS` — safe to re-run
- `tests/test_watermark.php` — 37-check CLI integration test suite
  - T1: DB column verification (14 columns across 2 tables)
  - T2: Source file presence on disk
  - T3: GD pipeline (apply, file written, original intact, size check)
  - T4: DB write and read-back (7 column values verified)
  - T5: Posting logic (processed_path vs file_path selection, fallback)
  - T6: Clear watermark (NULL in DB, original intact, _wm file preserved)
  - T7: history.php backward-compat SHOW COLUMNS guard

### Changed
- `public/business_profiles.php` — Brand Kit / Default Watermark section
  - Section redesigned as asset library only (not placement control)
  - Removed: position grid (3×3), opacity/size/padding sliders, Preview button
  - Kept: watermark source dropdown, custom watermark upload zone, enabled checkbox
  - Added: purple info note — *"Placement is configured per image in Content Editor"*
  - Removed JS: `setWmPositionActive()`, `previewWatermark()`
  - Removed CSS: `.wm-pos-btn` block
- `public/index.php` — per-image watermark panel in Content History modal
  - `refreshModalMedia()` — shows `processed_path` when `wm_enabled`, adds "⬡ Watermarked" badge
  - `buildWmPanel()` — per-image panel with asset picker, 3×3 position grid, opacity/size/margin sliders
  - `toggleWmPanel()` — one panel open at a time
  - `wmPreview()` / `wmApply()` / `wmClear()` — full preview/apply/clear cycle
  - `refreshRowThumb()` — fixed: now uses `processed_path` when `wm_enabled` (was using `file_path` always)
  - Purple (#a78bfa) colour theme for all watermark UI elements
- `api/business_profiles.php` — `bp_add()` / `bp_edit()` save `wm_*` brand kit fields
  - Backward-safe: `SHOW COLUMNS` guard before extended INSERT/UPDATE
  - `bp_edit()` preserves existing `wm_path` when no new file uploaded
- `api/history.php` — media SELECT conditionally includes `processed_path`, `wm_enabled`
  - `SHOW COLUMNS` guard — no crash before migration is run
- `api/post_now.php` — uses `processed_path` when `wm_enabled=1`, falls back to `file_path`
  - Removed: auto-inject watermark on post (now explicit per-image)
  - Backward-safe: `SHOW COLUMNS` guard
- `cron/post_scheduler.php` — same `processed_path` logic as `post_now.php`
  - `static $schedHasWmCols` caches SHOW COLUMNS check across loop iterations

### Architecture Notes
- **Business Profile = Brand Kit**: stores logo/watermark assets and optional defaults only
- **Content Editor = Placement**: per-image position, opacity, size, margin set in history modal
- **Posting**: uses watermarked copy (`processed_path`) only when user explicitly applied it (`wm_enabled=1`)
- **Original never modified**: `file_path` always preserved; `[name]_wm.[ext]` is separate copy
- `wm_settings_json` intentionally omitted — individual typed columns used instead (better queryability and type enforcement)

---

## [2.8.0] — 2026-05-15

### Added
- `config/crypto.php` — AES-256-CBC token encryption/decryption utility
  - `token_encrypt(string)` — encrypts with `enc:` prefix for backward compatibility
  - `token_decrypt(string)` — decrypts `enc:` values, passes through plaintext (legacy safe)
  - `token_mask(string)` — safe UI masking, shows 🔒 for encrypted values
  - `token_encryption_enabled()` — checks if key is configured
  - No-op if `TOKEN_ENCRYPTION_KEY` not set (safe for dev without key)
- `api/db_backup.php` — full database backup/restore endpoint
  - `action=export` — runs mysqldump, saves `mini_siska_YYYY-MM-DD_HHmmss.sql` to `backups/database/`
  - `action=list` — returns latest 20 backup files with size and date
  - `action=delete` — deletes a specific backup file (path traversal safe)
  - Auto-detects mysqldump at XAMPP, common paths, system PATH
  - Logs success/fail to `logs/backup.log`
- `database/migrate_encrypt_tokens.php` — CLI migration to encrypt existing plaintext tokens
- `docs/deployment.md` — full deployment guide: XAMPP→VPS, cPanel, Hostinger, ngrok, production checklist
- Token encryption health check in `api/dev_tools.php`

### Changed
- `api/social_accounts.php`
  - `sa_add()` — encrypts `access_token` via `token_encrypt()` before DB insert
  - `sa_edit()` — encrypts new token before DB update
  - `sa_test()` — decrypts token via `token_decrypt()` before passing to platform test functions
  - `sa_test_threads()` — stores re-encrypted token when auto-renewing
  - `mask_token()` — shows `🔒 encrypted` badge for `enc:` prefixed values
- `cron/post_scheduler.php` — `sched_load_account()` decrypts token after DB fetch
- `api/post_now.php` — `pn_load_account()` decrypts token after DB fetch
- `public/dashboard.php` — Developer Tools panel full upgrade:
  - Status chips row (DB, OpenAI, Disk, Uploads)
  - Backup Database button + backup file list with delete
  - Health check shows token encryption status
  - Git history still available
  - Quick reference card with encryption migration command
  - Links to deployment guide
- `public/manifest.json` — PWA improvements:
  - Added `shortcuts` (Generator, Dashboard, Accounts)
  - Added `display_override: ["standalone","minimal-ui","browser"]`
  - Split icons into separate `any` and `maskable` entries
  - Added `prefer_related_applications: false`
  - Added `dir` and `lang` fields
- `.env.example` — added `TOKEN_ENCRYPTION_KEY` template with generation command

---

## [2.7.0] — 2026-05-14

### Added
- `.gitignore` — comprehensive exclusions (secrets, uploads, logs, cache, node_modules, vendor)
- `README.md` — full project documentation with structure, quickstart, module table
- `CHANGELOG.md` — this file
- `VERSION.md` — version tracker with per-release module list
- `docs/git-workflow.md` — Git backup, rollback, restore guide for developers
- `git-backup.bat` — one-click Windows backup script (git add → commit → push)
- `git-backup.ps1` — PowerShell version with safety checks and commit message input
- `api/dev_tools.php` — system health check API (DB, OpenAI, env vars, disk, scheduler)
- Developer Tools panel in `public/dashboard.php` (health check, git backup trigger, API status)
- `.env.example` — safe credential template
- `backups/database/` and `backups/media/` folder structure with `.gitkeep`

### Changed
- `.gitignore` rewritten from garbled UTF-16 to clean UTF-8 with comprehensive rules

---

## [2.6.0] — 2026-05-13

### Added
- Structured JSON content output for all 5 platforms
- Per-platform field sets: FB (hook+script+cta+wa+hashtags+assembled), Instagram (hook+caption+cta+hashtags+assembled), Threads (hook+script+hashtags+assembled), Telegram (hook+script+cta+wa+hashtags+assembled), TikTok (hook+script+cta+caption+hashtags+assembled)
- `buildStructuredInstruction()` helper in `api/openai.php`
- `$structDefs` array with per-platform component descriptions
- `STRUCT_META` and `PLAT_FIELDS` constants in `public/index.php`
- `renderStructured()` and `copyStruct()` JS functions
- `#struct-section` and `#struct-grid` HTML in generator page
- Color-coded component cards: amber (hook), blue (script), green (cta), pink (caption), green (wa), purple (hashtags), gold (assembled)
- Character count badge on assembled card (warns over 500 for Threads)

### Changed
- `generate()` in `public/index.php` always sends `structured=1`
- `api/openai.php` uses `response_format: json_object` in structured mode
- Token budget: 1400 for structured, 1200 for variations, 250 for viral shortform

---

## [2.5.0] — 2026-05-12

### Added
- Expanded video format support: MOV, AVI, WebM, MKV, 3GP, 3G2, M4V, MPEG, WMV, OGV
- `find_ffmpeg()` function — auto-detects ffmpeg in winget, common paths, system PATH
- Auto-conversion: non-MP4 video → MP4 H.264 + AAC using ffmpeg on upload
- Upload response fields: `converted`, `convert_note`, `warn`
- `application/octet-stream` MIME fallback with extension-based type detection

### Changed
- `api/upload.php` completely rewritten with expanded MIME/extension whitelists

### Notes
- ffmpeg installed via `winget install --id Gyan.FFmpeg` (v8.1.1)
- winget path hardcoded as first candidate in `find_ffmpeg()`

---

## [2.4.0] — 2026-05-12

### Added
- `ma_resize_and_encode()` function in `api/media_analysis.php` — GD resize to 1024px max
- `response_format: json_object` in media analysis API payload

### Changed
- Media analysis max_tokens raised to 1000 (from 700)
- Media analysis temperature lowered to 0.5 (from 0.7)
- System prompt updated: "make your best inference — do not refuse"

### Fixed
- AI vision failures on large iPhone/DSLR photos (base64 payload too large)
- AI returning "I cannot analyze" instead of JSON when image quality low

---

## [2.3.0] — 2026-05-11

### Added
- Code 100 ("text too long") added to `th_graph_error()` mapping in `social/threads.php`

### Fixed
- Threads posts exceeding 500-character limit rejected by Meta API (error code 100)
- Auto-truncation added in `threads_send()` using `mb_strrpos` at word boundary
- Truncated posts end with `…` to signal truncation

---

## [2.2.0] — 2026-05-11

### Added
- 9 Threads writing modes: Relatable, Storytelling, Opinion, Soft Feminine, Daily Thoughts, Mood Posting, Casual Luxury, Random Thoughts, Viral Conversation
- 3 Threads length modes: Short (120w max), Medium (150-280w), Viral Shortform (3-5 lines)
- "Generate 3 Variations" button — produces emotional/funny/luxury versions in one API call
- `===VAR===` separator parsing for variation responses
- Threads mode/length selectors in `public/index.php` generator form
- Variations panel with `#variations-section` and 3 variation cards
- `generateVariations()`, `renderVariations()`, `copyVar()`, `useVar()` JS functions
- `onPlatformChange()` — shows/hides Threads panel, dims irrelevant controls

### Changed
- `prompts/threads_prompt.txt` completely rewritten for Threads-native voice
- Token budget: 1200 for variations, 250 for viral shortform

---

## [2.1.0] — 2026-05-10

### Added
- `th_refresh_token()` function in `social/threads.php` — calls Threads token refresh endpoint
- `cron/refresh_tokens.php` — batch token renewal for all Threads accounts expiring in < 30 days
- `cron/run_refresh_tokens.bat` — Windows Task Scheduler launcher
- Windows Task Scheduler task: "miniSISKA Token Refresh" (weekly, Sunday 03:00)
- Token expiry badge in `public/social_accounts.php` — green (>30d), orange (8-30d), red pulsing (≤7d)
- CSS classes: `.expiry-badge`, `.exp-ok`, `.exp-warn`, `.exp-critical`, `.exp-unknown`

### Changed
- `sa_list()` in `api/social_accounts.php` — adds `token_expires_at` and computed `token_days_left`
- `sa_add()` — sets `token_expires_at = NOW() + 60 days` when platform=threads
- `sa_edit()` — resets `token_expires_at` when new token saved
- `sa_test_threads()` — auto-renews token when `daysLeft < 30` or NULL

---

## [2.0.0] — 2026-05-08

### Added
- `api/media_analysis.php` — OpenAI GPT-4o vision analysis endpoint
- Analyze button in `public/index.php` (appears after media upload)
- AI analysis panel: product_category, material, color_style, luxury_vibe, emotional_vibe, target_audience, fashion_matching, selling_angle, suggested_product_name, suggested_product_details, suggested_extra_instruction, visual_summary, suggested_hooks (3), emotional_angle, luxury_angle, viral_angle
- Auto-fill: product name, details, extra instruction from AI analysis
- `window.aiAnalysis` stored and passed to `saveContent()`
- `ai_analysis TEXT` column added to `contents` table
- Video frame extraction client-side via Canvas API at t=1s

---

## [1.5.0] — 2026-05-07

### Added
- `api/upload.php` — file upload with finfo MIME validation
- `storage/uploads/.htaccess` — blocks PHP execution in uploads directory
- `api/social_accounts.php` — account CRUD + token management (list/add/edit/delete/test)
- `public/social_accounts.php` — full account manager UI
- `social/meta.php` — Facebook + Instagram Graph API posting
- `social/threads.php` — Threads Graph API posting
- `social/telegram.php` — Telegram Bot API posting
- `social/tiktok.php` — TikTok placeholder
- `social/post_dispatcher.php` — platform routing + dispatch log
- `api/queue.php` — cron HTTP endpoint for scheduler trigger
- Platform checkboxes (FB, Instagram, Threads, Telegram, TikTok) in generator UI
- Media preview after upload (image + video)
- Schedule date/time picker
- New DB columns: media_path, media_type, selected_platforms, scheduled_at, post_status, post_error, posted_at

---

## [1.2.0] — 2026-05-05

### Added
- 7 new output styles: softsell, hardsell, storytelling, viral_hook, elegant, bahasa_pasar, netizen
- Total: 12 output styles available

### Changed
- All platform prompts rewritten to remove numbered section labels
- Hard rule injected in all user messages: "Do not add section labels, headers, or numbered lists"

---

## [1.1.0] — 2026-05-04

### Added
- `config/database.php` — MySQLi connection singleton
- `database/schema.sql` — full table definition (contents, social_accounts)
- `api/save.php` — save generated content to DB
- `api/history.php` — content history list (paginated, filterable)
- `api/action.php` — approve/delete/schedule/mark_posted actions
- Content history panel in `public/index.php`
- Token usage bar (model, total tokens, prompt, completion)

---

## [1.0.0] — 2026-05-03

### Added
- `config/env.php` — three-layer .env parser (ENV → getenv → putenv)
- `api/openai.php` — OpenAI GPT-4o chat completions via cURL
- `public/index.php` — dark theme single-page app with AJAX form
- Platform prompts: `fb_prompt.txt`, `threads_prompt.txt`, `tiktok_prompt.txt`, `telegram_prompt.txt`
- `prompts/system_prompt.txt` — brand voice and persona
- `prompts/instagram_prompt.txt` — Instagram engine
- `.htaccess` — blocks `.env`, `config/`, `logs/`, `storage/`, `database/`
- `config/.htaccess` — deny all direct browser access
- `.gitignore` — excludes .env, logs/, storage/
- PWA: `public/manifest.json`, `public/sw.js`
