diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
index 0000000..5810a99
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,22 @@
+# Changelog
+
+All notable changes to vortex-mod-gallery will be documented here.
+Format: [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
+
+## [1.1.0] - 2026-07-17
+
+### Added
+- Declared `imgur_client_id` and `flickr_api_key` in the `[config]` manifest
+ section so the host can validate and persist them; the plugin already reads
+ both through `get_config` (MAT-134 R-01).
+- Manifest test pinning every config key read by `plugin_api.rs` to a
+ `[config]` declaration, and an ABI smoke assertion proving a configured
+ Imgur client id travels from `get_config` into the `Client-ID`
+ Authorization header.
+
+## [1.0.0] - 2026-04-15
+
+### Added
+- Initial release: Imgur, Flickr, and generic HTML gallery extraction with
+ `can_handle`, `supports_playlist`, `extract_links`, `extract_generic`, and
+ `is_http_url` exports.
diff --git a/Cargo.lock b/Cargo.lock
index 6f3fa5b..1898fcd 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -2238,7 +2238,7 @@ checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
[[package]]
name = "vortex-mod-gallery"
-version = "1.0.0"
+version = "1.1.0"
dependencies = [
"extism",
"extism-pdk",
diff --git a/Cargo.toml b/Cargo.toml
index 918b348..6853a78 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "vortex-mod-gallery"
-version = "1.0.0"
+version = "1.1.0"
edition = "2021"
description = "Gallery WASM plugin for Vortex — Imgur, Reddit, Flickr image galleries"
license = "GPL-3.0"
diff --git a/plugin.toml b/plugin.toml
index f0919ed..20fedb8 100644
--- a/plugin.toml
+++ b/plugin.toml
@@ -1,6 +1,6 @@
[plugin]
name = "vortex-mod-gallery"
-version = "1.0.0"
+version = "1.1.0"
category = "crawler"
author = "vortex-community"
description = "Image galleries: Imgur, Reddit, Flickr, generic scraping"
@@ -15,3 +15,5 @@ http = true
[config]
min_resolution = { type = "string", default = "800x600", description = "Filter by minimum image size (WxH)" }
auto_name = { type = "boolean", default = true, description = "Auto-generate filenames from provider metadata" }
+imgur_client_id = { type = "string", default = "", description = "Imgur API client ID (sent as 'Client-ID' authorization header)" }
+flickr_api_key = { type = "string", default = "", description = "Flickr REST API key" }
diff --git a/tests/manifest_config.rs b/tests/manifest_config.rs
new file mode 100644
index 0000000..217be0c
--- /dev/null
+++ b/tests/manifest_config.rs
@@ -0,0 +1,26 @@
+//! Guards the `[config]` contract: keys the plugin reads via `get_config`
+//! must be declared in `plugin.toml` so the host can validate and expose them.
+
+const MANIFEST: &str = include_str!("../plugin.toml");
+
+#[test]
+fn manifest_declares_every_config_key_read_by_the_plugin() {
+ let (_, config_section) = MANIFEST
+ .split_once("[config]")
+ .expect("plugin.toml must have a [config] section");
+ for key in [
+ "min_resolution",
+ "auto_name",
+ "imgur_client_id",
+ "flickr_api_key",
+ ] {
+ let declared = config_section.lines().any(|line| {
+ line.split_once('=')
+ .is_some_and(|(name, _)| name.trim() == key)
+ });
+ assert!(
+ declared,
+ "plugin.toml [config] is missing '{key}', which plugin_api.rs reads via get_config"
+ );
+ }
+}
diff --git a/tests/wasm_smoke.rs b/tests/wasm_smoke.rs
index dac6bc1..edfc8f8 100644
--- a/tests/wasm_smoke.rs
+++ b/tests/wasm_smoke.rs
@@ -11,6 +11,7 @@ const GENERIC_URL: &str = "https://example.test/gallery/page.html";
const GENERIC_IMAGE_URL: &str = "https://example.test/gallery/sample.jpg";
const IMGUR_BODY: &str = r#"{"data":[{"id":"img1","title":"sample","link":"https://i.imgur.com/img1.jpg","width":1920,"height":1080}],"status":200,"success":true}"#;
const GENERIC_BODY: &str = r#"
"#;
+const SMOKE_CLIENT_ID: &str = "SMOKE_CLIENT";
fn wasm_path() -> PathBuf {
let path = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join(WASM_REL_PATH);
@@ -38,6 +39,14 @@ fn stub_http_request() -> Function {
.as_str()
.ok_or_else(|| extism::Error::msg("http_request URL is missing"))?;
let body = if url.contains("api.imgur.com") {
+ // The configured client id must travel from `get_config`
+ // all the way into the Imgur Authorization header.
+ let auth = request["headers"]["Authorization"].as_str().unwrap_or("");
+ if auth != format!("Client-ID {SMOKE_CLIENT_ID}") {
+ return Err(extism::Error::msg(format!(
+ "imgur request carries wrong Authorization header: {auth:?}"
+ )));
+ }
IMGUR_BODY
} else if url == GENERIC_URL {
GENERIC_BODY
@@ -60,8 +69,17 @@ fn stub_get_config() -> Function {
[PTR],
[PTR],
UserData::<()>::default(),
- |plugin, _inputs, outputs, _user_data: UserData<()>| {
- let handle = plugin.memory_new("")?;
+ |plugin, inputs, outputs, _user_data: UserData<()>| {
+ let input = inputs[0]
+ .i64()
+ .ok_or_else(|| extism::Error::msg("get_config expected i64 input"))?;
+ let key: String = plugin.memory_get_val(&Val::I64(input))?;
+ let value = if key == "imgur_client_id" {
+ SMOKE_CLIENT_ID
+ } else {
+ ""
+ };
+ let handle = plugin.memory_new(value)?;
outputs[0] = Val::I64(handle.offset() as i64);
Ok(())
},