(C) 2025-2026 MWBM Partners Ltd
- Version Management
- How to Cut a Release
- Version Format Conventions
- Platform Version Mapping
- CI/CD Pipeline Overview
- GitHub Secrets Configuration
- Release Binary Hardening
- Dependency Bundling Requirements
- GitHub Projects Workflow
- Managing File Type Definitions
- Managing Metadata Tag Definitions
- File Integrity Checking
- Background Service Mode
- Settings Export / Import
- Test Mode (Safe Edit Mode)
- Pre-release Version Safety
- Privacy Policy
- Codec Registry
- JSON Schema Validation
- Apple Privacy Manifest
- App Store / TestFlight Distribution Checklist
- Workspace Lint Configuration
The canonical version lives in the root Cargo.toml under [workspace.package].version. All other version locations are derived from it:
| File | Format | Example |
|---|---|---|
Cargo.toml [workspace.package] |
Full semver | 2.0.0-alpha.2 |
windows/.../Package.appxmanifest Identity.Version |
4-part (no pre-release) | 2.0.0.0 |
macos/.../Info.plist CFBundleShortVersionString |
3-part (no pre-release) | 2.0.0 |
macos/.../Info.plist CFBundleVersion |
Build number | 1 (incremented per build) |
Version bumps are managed via the version-bump.yml GitHub Actions workflow. This ensures all version files stay in sync automatically.
How to trigger:
# Explicit version
gh workflow run version-bump.yml -f version=2.0.0-alpha.2
# Increment by type
gh workflow run version-bump.yml -f bump_type=patch
gh workflow run version-bump.yml -f bump_type=minor
gh workflow run version-bump.yml -f bump_type=pre-beta
# Also create a git tag
gh workflow run version-bump.yml -f version=2.0.0-beta.1 -f create_tag=true
# Skip PR, commit directly
gh workflow run version-bump.yml -f version=2.0.0-beta.1 -f create_pr=falseOr use the GitHub Actions UI: Actions > Version Bump > Run workflow.
The ci-rust.yml workflow includes a version-check job that verifies all platform version files match Cargo.toml. If versions drift out of sync, CI will fail with a clear error message pointing to the mismatched file.
-
Ensure all work is merged to
main- All milestone issues closed
- CI is green
-
Bump the version
gh workflow run version-bump.yml \ -f version=2.0.0-beta.1 \ -f create_tag=true \ -f create_pr=true
-
Review and merge the version bump PR
- Verify all version files are consistent
- Merge to
main
-
Push the tag (if not already created by the workflow)
git tag -a v2.0.0-beta.1 -m "Release v2.0.0-beta.1" git push origin v2.0.0-beta.1 -
The
release.ymlworkflow runs automatically on tag push:- Builds all platforms (macOS arm64, Windows x64/arm64, Linux x64/arm64)
- Generates SHA256 checksums
- Creates a draft GitHub Release with artifacts and release notes
-
Review the draft release on GitHub
- Edit release notes if needed
- Publish when ready
For urgent patches on a released version:
- Create a branch from the release tag:
git checkout -b hotfix/v2.0.1 v2.0.0 - Apply fixes
- Bump version:
gh workflow run version-bump.yml -f version=2.0.1 - Merge to
mainand tag
We follow Semantic Versioning 2.0.0:
MAJOR.MINOR.PATCH[-PRE_RELEASE]
| Label | Usage | Example |
|---|---|---|
alpha.N |
Early development, API unstable | 2.0.0-alpha.3 |
beta.N |
Feature-complete, bug-fixing phase | 2.0.0-beta.1 |
rc.N |
Release candidate, final testing | 2.0.0-rc.2 |
| (none) | Stable release | 2.0.0 |
| Milestone | Version | Status |
|---|---|---|
| M0 — Repository Setup | v0.1.0 |
✅ Released |
| M1 — Core Engine | v0.2.0 |
✅ Released |
| M2 — Rule Engine | v0.3.0 |
✅ Released |
| M3 — CLI | v0.4.0 |
✅ Released |
| M4 — FFI + UI Shells | v0.5.0 |
✅ Released |
| M5 — Providers | v0.6.0 |
✅ Released |
| M6 — Full Native UI | v0.7.0 |
✅ Released |
| M7 — Cloud Storage | v0.8.0 |
✅ Released |
| M8 — Packaging | v0.9.0 |
✅ Released |
| M9 — Database Export | v0.10.0 |
✅ Released |
| M10 — Public Release | v1.0.0 |
✅ Released |
Note: The project uses
v0.x.0pre-release versioning through M9.v1.0.0is reserved for the first public release at M10.
MSIX uses 4-part versioning (Major.Minor.Build.Revision). Pre-release labels are stripped:
| Semver | MSIX |
|---|---|
2.0.0-alpha.1 |
2.0.0.0 |
2.0.0-beta.3 |
2.0.0.0 |
2.0.0 |
2.0.0.0 |
2.1.0 |
2.1.0.0 |
The 4th component (.0) is reserved for future use (e.g., build numbers).
CFBundleShortVersionString: 3-part version, pre-release stripped (e.g.,2.0.0)CFBundleVersion: Integer build number, incremented each build (e.g.,1,2,3)
Apple requires CFBundleShortVersionString to be a valid X.Y.Z format for App Store submission.
| Workflow | File | Trigger | Purpose |
|---|---|---|---|
| Rust Core CI | ci-rust.yml |
Push/PR to main (crates/**) |
Format, lint, test, version-sync |
| macOS CI | ci-macos.yml |
Push/PR to main (macos/**) |
Build SwiftUI app |
| Windows CI | ci-windows.yml |
Push/PR to main (windows/**) |
Build WinUI 3 app |
| Linux CI | ci-linux.yml |
Push/PR to main (crates/mm-gtk/**) |
Build GTK4 app under Xvfb |
| Version Bump | version-bump.yml |
Manual (workflow_dispatch) |
Bump version across all files |
| Release Build | release.yml |
Tag push (v*) |
Build all platforms, create release |
| Security Audit | audit.yml |
Weekly + push to main |
cargo deny + cargo audit |
| Documentation | docs.yml |
Push to main (crates/**) |
Generate cargo doc |
The release workflow (release.yml) runs 5 parallel build jobs + 1 final release job:
prepare ──┬── release-macos (arm64)
├── release-windows-x64
├── release-windows-arm64
├── release-linux-x64
└── release-linux-arm64
│
create-release (draft GitHub Release)
Artifact naming convention:
MeedyaManager-{version}-{platform}-{arch}.tar.gz
MeedyaManager-{version}-{platform}-{arch}.sha256
MeedyaManager-{version}-SHA256SUMS.txt
| Platform | Status | Requirement |
|---|---|---|
| macOS | Implemented | Apple Developer ID cert (APPLE_CERT_P12 secret) + notarisation |
| Windows | Implemented | Authenticode PFX cert (WINDOWS_CERT_PFX secret) via signtool |
| Linux | N/A | Not required for Flatpak/Snap distribution |
All code signing and release credentials are stored as GitHub repository
secrets (Settings → Secrets and variables → Actions → Repository secrets).
The release.yml workflow reads these automatically during tag-triggered
release builds. CI builds do not require secrets — signing is skipped with
a ::warning:: annotation when a secret is absent.
- Go to the repository on GitHub
- Click Settings → Secrets and variables → Actions
- Click New repository secret
- Enter the Name and Value exactly as shown below
- Click Add secret
Apple requires all distributed macOS apps to be:
- Code-signed with a Developer ID Application certificate
- Notarised by Apple's notary service
- Stapled — the notarisation ticket attached to the DMG
Without signing and notarisation, Gatekeeper blocks the app on macOS 12+.
| Secret name | Description | How to obtain |
|---|---|---|
APPLE_DEVELOPER_ID |
Full name string of the Developer ID Application certificate | Keychain Access → find "Developer ID Application: …" — copy the exact name including Team ID in parentheses |
APPLE_TEAM_ID |
10-character Apple Team ID | developer.apple.com/account → Membership → Team ID |
APPLE_ID |
Apple ID email address used for the Developer Program | The email you use to sign in to developer.apple.com |
APPLE_APP_PASSWORD |
App-specific password for notarytool |
appleid.apple.com → Sign-In and Security → App-Specific Passwords → Generate |
APPLE_CERT_P12 |
Base64-encoded Developer ID Application certificate + private key (.p12 / .pfx) |
Export from Keychain Access → Base64-encode: base64 -i cert.p12 |
APPLE_CERT_PASSWORD |
Password protecting the .p12 file |
The password set when exporting from Keychain Access |
# 1. Open Keychain Access → find "Developer ID Application: MWBM Partners Ltd (XXXXXXXXXX)"
# 2. Right-click → Export → save as cert.p12, set a strong password
# 3. Base64-encode for the secret value:
base64 -i cert.p12 | pbcopy # macOS — copies to clipboard
base64 -w0 cert.p12 # Linux — prints single-line base64
# 4. Paste the base64 string as the APPLE_CERT_P12 secret value
# 5. Store the export password as APPLE_CERT_PASSWORD1. Go to appleid.apple.com → Sign-In and Security → App-Specific Passwords
2. Click "+" → name it "MeedyaManager CI Notarisation"
3. Copy the generated password (shown only once)
4. Store it as APPLE_APP_PASSWORD
create-dmg.shassembles the.appbundlecodesign --deep --options runtimesigns the bundle with the Developer ID certificatexcrun notarytool submituploads the DMG to Apple's notary service and waits for approvalxcrun stapler stapleattaches the notarisation ticket to the DMG- The signed, notarised DMG is uploaded as a release artifact
Windows recommends (and Microsoft Store requires) that MSIX packages and binaries are signed with an Authenticode certificate. Without signing, SmartScreen shows a warning on first launch.
| Secret name | Description | How to obtain |
|---|---|---|
WINDOWS_CERT_PFX |
Base64-encoded code signing certificate + private key (.pfx / .p12) |
Purchase an EV Code Signing certificate from DigiCert, Sectigo, or GlobalSign; export as .pfx; Base64-encode: certutil -encode cert.pfx cert.b64 or base64 -w0 cert.pfx |
WINDOWS_CERT_PASSWORD |
Password protecting the .pfx file |
Set when exporting or purchasing the certificate |
# PowerShell — base64-encode the PFX, copy to clipboard
[Convert]::ToBase64String([IO.File]::ReadAllBytes("cert.pfx")) | Set-Clipboard# bash (Linux/WSL)
base64 -w0 cert.pfx- The Base64 value from
WINDOWS_CERT_PFXis decoded to a temporary.pfxfile signtool.exe sign /fd SHA256 /td SHA256 /tr http://timestamp.digicert.comsigns all.exeand.dllfiles with a trusted timestamp- The temporary
.pfxis securely deleted from the runner after signing - Signed binaries are packaged into the release artifact
The Windows package identity (Package.appxmanifest Identity.Name) is
ltd.MWBMpartners.MeedyaManager. When submitting to the Microsoft Store,
ensure this name is registered in Partner Center under your Publisher account.
Flatpak packages distributed via Flathub are signed by Flathub's GPG key, not by the developer. Snap packages distributed via the Snap Store are signed by Canonical's infrastructure.
For standalone .deb / AppImage / .tar.gz releases, SHA256 checksums are
generated and published alongside each artifact — users can verify integrity
without a code signature.
| Secret | Required for | Platform |
|---|---|---|
APPLE_DEVELOPER_ID |
Code signing | macOS |
APPLE_TEAM_ID |
Notarisation | macOS |
APPLE_ID |
Notarisation | macOS |
APPLE_APP_PASSWORD |
Notarisation | macOS |
APPLE_CERT_P12 |
Certificate import (future CI improvement) | macOS |
APPLE_CERT_PASSWORD |
Certificate import (future CI improvement) | macOS |
WINDOWS_CERT_PFX |
Authenticode signing | Windows |
WINDOWS_CERT_PASSWORD |
Authenticode signing | Windows |
Security note: Never commit certificate files or private keys to the repository. Store them exclusively as GitHub repository secrets. Rotate certificates annually or when a team member with key access leaves.
All release and dist profile builds include hardening flags that reduce
binary size, improve runtime performance, and remove debug information from
shipped artifacts. This is compliant with all platform store guidelines and
with the GPL-2.0-or-later licence (source code remains fully available).
| Profile | Use case | Key flags |
|---|---|---|
dev |
Local development | opt-level=0, debug=true, incremental |
release |
Release builds | opt-level=3, lto=fat, strip=symbols, panic=abort |
dist |
Final shipped artifacts | inherits release + strip=debuginfo |
test |
Test runs | opt-level=1, debug=true |
| Flag | Effect | Platform compliance |
|---|---|---|
opt-level = 3 |
Maximum compiler speed optimisations | All platforms |
lto = "fat" |
Cross-crate link-time optimisation — dead code elimination | All platforms |
codegen-units = 1 |
Single codegen unit for maximum LTO effectiveness | All platforms |
strip = "symbols" |
Remove symbol table from binary (~30–60% size reduction) | All platforms |
strip = "debuginfo" |
Remove DWARF debug info as well (dist profile only) | All platforms |
panic = "abort" |
No unwinding machinery — smaller binary, no stack unwind tables | All platforms |
debug = false |
No embedded debug information | All platforms |
incremental = false |
Reproducible builds (same input → same output) | All platforms |
- Hardened Runtime —
MeedyaManager.entitlementsenforces:com.apple.security.app-sandbox = true— sandboxed executioncom.apple.security.hardened-runtime = true— JIT disabled, library validation on
- Notarisation — all
.dmgreleases notarised via Apple notary service - Code signing — Developer ID certificate required for Gatekeeper
- MSIX packaging — authenticode signing via WinAppSDK build pipeline
- DEP/ASLR — enforced automatically for all managed (.NET/WinUI 3) code
- Integrity Level — MSIX packages run at
Medium ILby default
- PIE (Position-Independent Executable) — Rust enables this by default on Linux
- RELRO / BIND_NOW — enabled by default in the Rust linker on ELF targets
- Strip — the
cargo build --profile diststep strips all symbols - Flatpak sandboxing —
strictconfinement via portal permissions
| Technique | Reason not used |
|---|---|
| LLVM obfuscation / obfuscator-llvm | GPL-2.0-or-later requires source availability; obfuscation conflicts with the spirit and legal requirements of the licence |
| Binary packing (UPX) | Triggers antivirus false positives; breaks code signing on macOS/Windows |
| Anti-debugging traps | Not permitted by Apple App Store / Microsoft Store ToS |
| String encryption | Incompatible with GPL source requirements; adds runtime overhead |
# Development build (fast, with debug info)
cargo build
# Optimised release build (shipped in CI release workflow)
cargo build --release
# Full distribution build (final shipped artifacts)
cargo build --profile dist
# Check binary size after stripping
size target/release/meedya
file target/release/meedyaMeedyaManager must ship as a self-contained application on all three platforms. Users must not need to install any runtime, SDK, or library separately.
| Platform | External Dependency | Bundled How |
|---|---|---|
| All | Rust crate dependencies | Statically linked at compile time via Cargo — zero .dll/.dylib/.so from Cargo crates |
| macOS | libmm_ffi.dylib (Rust FFI bridge) |
Placed in MeedyaManager.app/Contents/Frameworks/ by create-dmg.sh |
| macOS | System frameworks (SwiftUI, Foundation, Security) | Provided by macOS — no bundling required |
| Windows | mm_ffi.dll (Rust FFI bridge) |
Included via <Content> in MeedyaManager.csproj; copied to publish output |
| Windows | Windows App SDK runtime | <WindowsAppSDKSelfContained>true</WindowsAppSDKSelfContained> in .csproj bundles the runtime in the MSIX |
| Linux | GTK4, Libadwaita | Provided by the org.gnome.Platform//47 Flatpak runtime — not bundled |
| Linux | All Rust dependencies | Statically compiled into the mm-gtk binary by Cargo |
libmm_ffi.dylibis signed individually (codesign --options runtime) before the outer bundle is signed with--deep. This is required for Hardened Runtime notarisation.- Entitlements (
macos/MeedyaManager.entitlements):app-sandbox = true— required for Mac App Store submissionfiles.user-selected.read-write— grants access to files chosen via open panelsnetwork.client— outbound network for metadata providers and cloud APIskeychain-access-groups— allows thekeyringcrate to read/write API credentials from the macOS Keychain. The$(AppIdentifierPrefix)variable is substituted bycodesign.
- Mac App Store vs Direct Distribution: The current build targets Direct Distribution via a notarised DMG. For Mac App Store submission, an Xcode project (
.xcodeproj) is required alongside the SwiftPM package. This is tracked separately. reqwestTLS: Uses therustls-tlsfeature — OpenSSL is not required and not linked dynamically.- GPL-2.0 licence: The
LICENSEfile is copied intoContents/Resources/LICENSEbycreate-dmg.sh.
mm_ffi.dllmust be built (cargo build -p mm-ffi --release) beforedotnet publish. The.csprojincludes it via a conditional<Content>element.- Windows App SDK:
<WindowsAppSDKSelfContained>true</WindowsAppSDKSelfContained>causes the SDK to be bundled inside the MSIX, eliminating the need for users to install the Windows App Runtime separately. - Microsoft Store: For Store submission, use the MSIX package (already configured). The Store manages the Windows App Runtime dependency automatically when
WindowsAppSDKSelfContainedis false. For direct distribution, self-contained is preferred. - Authenticode signing:
signtool.exeis run inrelease.ymlusing theWINDOWS_CERT_PFXandWINDOWS_CERT_PASSWORDsecrets. See GitHub Secrets Configuration. - GPL-2.0 licence: The
LICENSEfile is included via<Content>in the.csprojand deployed alongside the executable.
-
GNOME Platform runtime (
org.gnome.Platform//47) provides GTK4 (4.14), Libadwaita (1.5), and all GNOME libraries. These are not bundled inside the Flatpak. -
Rust dependencies: All Cargo crates are statically linked into the
mm-gtkbinary. The vendor archive (vendor.tar.gz) must be regenerated and committed when dependencies change:cargo vendor vendor tar czf vendor.tar.gz vendor/ # Update sha256 in the Flatpak YAML sha256sum vendor.tar.gz -
libmm_ffi.so: Not required for the Linux GTK4 build —mm-gtklinks directly tomm-coreas a Cargo workspace dependency without crossing an FFI boundary. -
GPL-2.0 licence: Installed to
${FLATPAK_DEST}/share/licenses/ltd.MWBMpartners.MeedyaManager/LICENSEby thedesktop-integrationFlatpak module. -
Flathub compliance: The Flathub submission review checks that:
- The app ID matches the manifest,
.desktop,.metainfo.xml, and icon filenames. - The vendor archive is reproducible and SHA256-pinned.
- No outbound network access is made during the build.
- AppStream
<metadata_license>is FSFAP or CC0;<project_license>is GPL-2.0-or-later.
- The app ID matches the manifest,
- Snap:
linux/snap/snapcraft.yamlpackages the binary withconfinement: strict. Rust builds produce statically linked binaries, so no extra stage-packages are needed beyond GTK4 (libgtk-4-1,libadwaita-1-0). - AppImage:
linux/appimage/build-appimage.shusesappimagetoolto wrap the binary with its GTK4 dependencies into a portable*.AppImage. The AppDir includes the GTK4/Libadwaita shared libraries from the build host.
We use GitHub Projects v2 to track all work. The board is at: MeedyaManager v2.0 — Rust Rewrite.
- Create issue before starting work (assigned to milestone, labeled, added to project)
- Move to In Progress on the project board when starting
- Link PRs to the issue (
Closes #Nin PR description) - Move to Done when the PR is merged and verified
- Close issue with a comment noting what was delivered
milestone:M0throughmilestone:M10— which milestoneplatform:core,platform:macos,platform:windows,platform:linux,platform:cli,platform:alltype:feature,type:bug,type:chore,type:docs,type:cipriority:P0(critical) throughpriority:P3(low)
just version # Show current version
just check # Run format + lint + tests
just build # Build all Rust crates
just build-release # Build in release mode
just release-local # Build release artifacts locally
just test # Run all tests
just lint # Clippy lints
just fmt # Auto-format code
just audit # Security + license audit
just docs # Generate API docsAll file-type classifications (audio, video, subtitle, companion) are stored in
config/filetypes.json5 at the workspace root. This file is:
- Embedded into every binary at compile time via
include_str!(). - Overridable at runtime: place a modified copy at
~/.config/meedyamanager/filetypes.json5(Linux/macOS) or%APPDATA%\MeedyaManager\filetypes.json5(Windows).
-
Open
config/filetypes.json5. -
Find the correct section (
audio,video,subtitle, orcompanion). -
Add a JSON5 object following the documented schema at the top of the file:
// Audio example { "ext": "xyz", "mime": "audio/x-xyz", "name": "XYZ Format", "lossless": false }, // Companion example (scope: "track" | "album" | "artist") { "ext": "notes", "name": "Track Notes", "scope": "track" },
-
Run
cargo test -p mm-core -- filetypeto verify no uniqueness invariants are broken. -
Commit the updated JSON5 file — the binary re-embeds it at the next build.
Add "enabled": false to the entry. The format is ignored by all lookups.
| Section | Field | Required | Type | Notes |
|---|---|---|---|---|
| all | ext |
✅ | string | Lowercase, no leading dot |
| all | name |
✅ | string | Human-readable display name |
| all | mime |
❌ | string/null | IANA MIME type |
| all | enabled |
❌ | bool | Default true |
| audio | lossless |
✅ | bool | true for lossless formats |
| subtitle | kind |
✅ | string | "subtitle" | "caption" | "lyrics" | "transcript" |
| companion | scope |
✅ | string | "track" | "album" | "artist" |
All known metadata tag definitions are stored in config/tags.json5.
Like filetypes.json5, it is embedded at compile time and user-overridable at
runtime.
Add an entry to the tags array with the required fields:
{
"id": "my_tag", // internal key (lowercase_snake_case)
"name": "MyTag", // template display name (used as <MyTag>)
"desc": "My custom tag description",
"category": "core", // core|sort|extended|classical|replaygain|encoding|podcast|virtual
"multi": false, // true if multiple values are common
// Optional format-specific keys (documentation only):
"id3": "TXXX:MYTAG", "vorbis": "MYTAG", "mp4": null, "ape": "MyTag"
}Custom tags are added to the custom array in your user override file
(~/.config/meedyamanager/tags.json5), not to the codebase file:
{
"custom": [
{
"id": "custom_rating",
"name": "Rating",
"desc": "Personal star rating 1–5",
"raw_key": "MEEDYAMETA_RATING"
}
]
}The raw_key is the actual tag key written into the file:
- FLAC/Ogg: Vorbis comment with key
MEEDYAMETA_RATING - MP3: ID3v2
TXXXframe with descriptionMEEDYAMETA_RATING - MP4/M4A: free-form atom
----:com.meedyamanager:MEEDYAMETA_RATING - APE: APE item with key
MEEDYAMETA_RATING
Custom tags are also available in rename templates as <Rating> once defined.
MeedyaManager uses atomic, integrity-checked writes for all metadata operations. This prevents file corruption from power failures or mid-write crashes.
Flow (mm_core::integrity::write_tags_safe):
- Compute SHA256 of the original file.
- Copy original →
<filename>.meedya_tmp(same directory for atomic rename). - Write updated tags into the temp file via
lofty. - Compute SHA256 of the temp file.
rename(2)temp file over original (atomic on same filesystem).- Log before/after hashes to
tracing.
If any step fails, the temp file is deleted and the original is untouched.
Corruption log: persistent failures are appended to
~/.config/meedyamanager/corruption.log with a timestamp, file path, and
error message.
MeedyaManager can run as an OS background service to continuously monitor watch folders and auto-organise media.
| Platform | Mechanism | Unit/Config Location |
|---|---|---|
| Linux | systemd user service | ~/.config/systemd/user/meedyamanager.service |
| macOS | launchd user agent | ~/Library/LaunchAgents/com.mwbm.meedyamanager.plist |
| Windows | Windows Service via sc.exe |
Windows Service Control Manager |
meedya service install # Register and enable at login
meedya service start # Start immediately
meedya service stop # Stop
meedya service status # Check if running
meedya service uninstall # Remove registrationThe service runs meedya watch --organize at background/idle CPU priority,
minimising impact on interactive use.
Template files for the unit/plist are in platform/linux/ and platform/macos/.
A .mmprofile file is a portable JSON bundle containing:
- Full
AppConfig(watch folders, rename rules, provider API keys, etc.) - Custom
filetypes.json5override (if present) - Custom
tags.json5override (if present) - Bundle version and creation timestamp
# Export current configuration
meedya config export ~/my-settings.mmprofile
# Import on a new machine
meedya config import ~/my-settings.mmprofileSecurity note: .mmprofile bundles may contain API keys. Do not share them publicly.
The bundle format is standard JSON (not JSON5) for maximum tool compatibility. Import is atomic — all files are written via temp-file+rename to prevent partial updates.
Test Mode prevents MeedyaManager from modifying original media files during
edit/tag operations. When enabled, all writes create a duplicate with a
_MeedyaManager suffix (e.g. track.mp3 → track_MeedyaManager.mp3).
| Component | File | Notes |
|---|---|---|
| Core module | crates/mm-core/src/test_mode.rs |
Manifest, path helpers, enable/disable, commit/revert |
| Integrity integration | crates/mm-core/src/integrity.rs |
write_tags_safe() delegates to write_tags_test_mode() when active |
| Config field | crates/mm-core/src/config/mod.rs |
test_mode: bool + MM_TEST_MODE env override |
| CLI command | crates/mm-cli/src/commands/config_cmd.rs |
meedya config test-mode on/off/status/commit/revert |
| Manifest file | <config_dir>/meedyamanager/testmode_manifest.json |
Persists across sessions |
When the user disables Test Mode and tracked files exist:
- Yes (commit): delete originals, rename copies (remove
_MeedyaManagersuffix) - No (revert): keep both originals and copies, clear manifest
The prompt applies to all tracked files, not just those from the current session.
Pre-release builds (semver pre-release label present, e.g. 1.3.0-beta.1)
auto-enable Test Mode on first launch.
Detection uses semver::Version::pre.is_empty() in
crates/mm-core/src/test_mode.rs::is_prerelease_version().
On upgrade to a stable release, if Test Mode is still enabled, the user is prompted to disable it (with the usual commit/revert choice).
The privacy policy at help/privacy-policy.md is required for Apple App Store
and Google Play submission. It covers:
- No analytics, telemetry, or tracking
- Local-only data storage (config, manifest, corruption log, OS keyring)
- Third-party provider data disclosure (search queries sent to enabled providers)
- Links to each provider's own privacy policy
- Open-source code availability
All platform UIs include a "Privacy Policy" link in Settings / About.
The codec registry is a separate developer-only reference file that maps audio/video codecs (the actual encoding algorithms) independently of file extensions. This enables:
- Tagging capability detection at the codec level (e.g. bare
.ac3streams are not taggable, but AAC inside.m4ais) - Accurate quality classification for container-wrapped streams (MKV/MP4/TS can carry many different codecs)
- Surround sound / spatial audio detection via
max_channels - Future use: transcoding advice, provider match scoring, codec-aware rename templates
See config/schemas/codecs.schema.json for the full JSON Schema definition.
| Concern | Filetype Registry | Codec Registry |
|---|---|---|
| Scope | File extensions | Encoding algorithms |
| User override | Via Settings UI (v1.3.0) | No — dev-only |
| Embedded | include_str!() |
include_str!() |
| Runtime override | MEEDYA_FILETYPES_OVERRIDE env var (dev only, v1.3.0) |
None |
Tracked by GitHub Issue #151.
All JSON5 configuration files have corresponding JSON Schema definitions
in config/schemas/:
| Config File | Schema File | Purpose |
|---|---|---|
config/filetypes.json5 |
config/schemas/filetypes.schema.json |
File type registry validation |
config/tags.json5 |
config/schemas/tags.schema.json |
Metadata tag registry validation |
config/settings.json5 |
config/schemas/settings.schema.json |
User settings validation |
config/codecs.json5 (v1.3.0) |
config/schemas/codecs.schema.json |
Codec registry validation |
All schemas use JSON Schema Draft 2020-12 (https://json-schema.org/draft/2020-12/schema).
VS Code users can associate JSON5 files with their schemas in
.vscode/settings.json:
{
"json.schemas": [
{
"fileMatch": ["config/filetypes.json5"],
"url": "./config/schemas/filetypes.schema.json"
},
{
"fileMatch": ["config/tags.json5"],
"url": "./config/schemas/tags.schema.json"
},
{
"fileMatch": ["config/settings.json5"],
"url": "./config/schemas/settings.schema.json"
}
]
}The ci-rust.yml workflow can validate config files against schemas using
check-jsonschema (Python) or ajv-cli (Node):
pip install check-jsonschema
check-jsonschema --schemafile config/schemas/filetypes.schema.json config/filetypes.json5The schemas are reference documentation; Rust-side validation is performed by
serde/json5 deserialization into strongly-typed structs. The schemas
ensure that external tools and editors can validate files before they reach
the Rust deserializer.
Since Spring 2024 (Xcode 15.3), Apple requires a privacy manifest for all apps submitted to the App Store or TestFlight. MeedyaManager's manifest is at:
macos/MeedyaManager/PrivacyInfo.xcprivacy
| API Category | Reason Code | Why |
|---|---|---|
| File Timestamp | C617.1 | File watcher detects new/changed media by modification time |
| Disk Space | E174.1 | Health check verifies sufficient space before writes |
| User Defaults | CA92.1 | SwiftUI persists UI preferences (window, tab, theme) |
MeedyaManager collects no user data and performs no tracking.
-
Info.plistwith validCFBundleIdentifier(ltd.MWBMpartners.MeedyaManager) -
MeedyaManager.entitlementswith App Sandbox enabled -
PrivacyInfo.xcprivacyprivacy manifest - Code signing with Developer ID Application certificate
- Notarisation via
xcrun notarytool - Hardened Runtime enabled
-
LSApplicationCategoryTypeset (public.app-category.utilities) -
LSMinimumSystemVersionset (15.0) - GPL-2.0
LICENSEincluded inContents/Resources/ - Xcode project (
.xcodeproj) — required for Mac App Store submission alongside the SwiftPM package (direct distribution uses SPM only) - App Store Connect — create app record, screenshots, description
- TestFlight — upload build via
xcodebuildor Transporter
- MSIX package with valid
Identity.Name(ltd.MWBMpartners.MeedyaManager) - Authenticode signing with EV certificate
- Windows App SDK self-contained bundling
-
Package.appxmanifestconfigured - Partner Center — register app identity, upload MSIX
- Flatpak manifest (
ltd.MWBMpartners.MeedyaManager.yml) - AppStream
metainfo.xmlmetadata -
.desktoplauncher file - Snap
snapcraft.yaml - Flathub — submit PR to flathub/flathub repository
- Snap Store — register snap name, upload
Chrome OS can run Linux apps via Crostini. Distribution options:
- Flatpak via Flathub — works out-of-box on Crostini (recommended)
- Android APK — would require a separate Android/Kotlin UI (not planned)
- PWA — the
mm-serverweb UI could be wrapped as a PWA (future)
As of v1.3.1, the project uses Cargo's [workspace.lints] feature to share lint
configuration across all 8 workspace crates.
- Root
Cargo.tomldefines[workspace.lints.clippy]and[workspace.lints.rust] - Each crate's
Cargo.tomlinherits via[lints] workspace = true - CI runs
cargo clippy --workspace --all-targetswith zero warnings enforced
| Group | Level | Purpose |
|---|---|---|
clippy::pedantic |
warn | Stricter code quality checks beyond default clippy |
clippy::nursery |
warn | Experimental lints catching common mistakes |
Over 25 specific lints are allowed at the workspace level. Each has a documented
rationale in Cargo.toml. Common categories:
- Noisy/low-value:
module_name_repetitions,must_use_candidate,doc_markdown - Config struct patterns:
struct_excessive_bools,too_many_lines - Numeric casts (audio/video metadata):
cast_possible_truncation,cast_sign_loss,cast_precision_loss - FFI/test patterns:
unsafe_code(warn; mm-ffi explicitly allows) - Style choices:
items_after_statements,manual_let_else,match_same_arms
To allow a new lint workspace-wide:
- Add it to
[workspace.lints.clippy]in rootCargo.tomlwith a comment explaining why - Run
cargo clippy --workspace --all-targetsto verify 0 warnings - Commit with a descriptive message
Individual crates can override workspace lints. For example, mm-ffi uses
#![allow(unsafe_code)] because FFI requires unsafe code by nature.
Test modules use #[allow(unsafe_code)] for set_var/remove_var (unsafe in Edition 2024).