Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# Changelog

## 1.0.31 - 2026-10-03

- Stop hot reload from scanning Composer dependencies or reacting to app log
writes. Source edits still refresh immediately, while development logs can
no longer trigger an endless reload loop during a clean first run.

## 1.0.30 - 2026-10-03

- Resolve the SDK from the installed Composer package and install the Android
Expand Down
6 changes: 3 additions & 3 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ members = [
exclude = ["pam-cli"]

[workspace.package]
version = "1.0.30"
version = "1.0.31"
edition = "2024"
rust-version = "1.88"
license = "Apache-2.0"
Expand Down
56 changes: 46 additions & 10 deletions crates/pam-native-cli/src/mobile.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7717,20 +7717,27 @@ fn http_response(stream: &mut TcpStream, content_type: &str, body: &[u8]) -> Res
}

fn project_fingerprint(root: &Path) -> Result<(u64, u128), String> {
fn visit(root: &Path, count: &mut u64, latest: &mut u128) -> Result<(), String> {
for entry in fs::read_dir(root).map_err(|error| error.to_string())? {
fn visit(
project_root: &Path,
directory: &Path,
count: &mut u64,
latest: &mut u128,
) -> Result<(), String> {
for entry in fs::read_dir(directory).map_err(|error| error.to_string())? {
let entry = entry.map_err(|error| error.to_string())?;
let relative = entry
.path()
.strip_prefix(root)
.unwrap_or(&entry.path())
.to_path_buf();
if ignored_project_path(&relative) {
let path = entry.path();
let relative = path.strip_prefix(project_root).unwrap_or(&path);
if ignored_project_path(relative)
|| relative
.components()
.any(|component| component.as_os_str() == "vendor")
|| path.extension() == Some(OsStr::new("log"))
{
continue;
}
let metadata = entry.metadata().map_err(|error| error.to_string())?;
if metadata.is_dir() {
visit(&entry.path(), count, latest)?;
visit(project_root, &path, count, latest)?;
} else if metadata.is_file() {
*count = count.saturating_add(metadata.len()).saturating_add(1);
let changed = metadata
Expand All @@ -7746,7 +7753,7 @@ fn project_fingerprint(root: &Path) -> Result<(u64, u128), String> {
}
let mut count = 0;
let mut latest = 0;
visit(root, &mut count, &mut latest)?;
visit(root, root, &mut count, &mut latest)?;
Ok((count, latest))
}

Expand Down Expand Up @@ -7784,6 +7791,35 @@ fn print_usage() {
mod tests {
use super::*;

#[test]
fn hot_reload_watches_sources_without_reloading_for_logs_or_vendor() {
let root = std::env::temp_dir().join(format!(
"pam-hot-reload-watch-{}",
SystemTime::now()
.duration_since(SystemTime::UNIX_EPOCH)
.expect("clock")
.as_nanos()
));
fs::create_dir_all(root.join("src")).expect("source directory");
fs::create_dir_all(root.join("vendor/package")).expect("vendor directory");
fs::write(root.join("index.php"), b"<?php").expect("entry");

let original = project_fingerprint(&root).expect("initial fingerprint");
fs::write(root.join("community-dev.log"), b"Reload ready\n").expect("development log");
fs::write(root.join("vendor/package/Library.php"), b"<?php").expect("dependency");
assert_eq!(
project_fingerprint(&root).expect("ignored changes"),
original
);

fs::write(root.join("src/App.php"), b"<?php class App {}").expect("source change");
assert_ne!(
project_fingerprint(&root).expect("source fingerprint"),
original
);
fs::remove_dir_all(root).expect("cleanup");
}

#[test]
fn release_run_uses_the_minified_locally_installable_variant() {
assert_eq!(BuildMode::Release.gradle_task(true), "assembleBenchmark");
Expand Down
2 changes: 1 addition & 1 deletion packages/native/src/Protocol.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

final class Protocol
{
public const string SDK_VERSION = '1.0.30';
public const string SDK_VERSION = '1.0.31';
public const int ABI_VERSION = 1;
public const int MINIMUM_VERSION = 1;
public const int VERSION = 1;
Expand Down
4 changes: 2 additions & 2 deletions packages/native/tests/run.php
Original file line number Diff line number Diff line change
Expand Up @@ -6686,8 +6686,8 @@ public function render(): Renderable
'Permanent drawer callbacks must not leak an open modal drawer into the compact layout after rotation.',
);
$assert(
\Pam\Native\Protocol::SDK_VERSION === '1.0.30',
'The runtime SDK contract must match the 1.0.30 release candidate.',
\Pam\Native\Protocol::SDK_VERSION === '1.0.31',
'The runtime SDK contract must match the 1.0.31 release candidate.',
);
$protocolReport = \Pam\Native\Protocol::negotiate(new \Pam\Native\ProtocolHandshake(
abiVersion: 1,
Expand Down
Loading