Notes were written to a file and shown back nowhere — the shape trials.py's own docstring calls this project's signature failure in a new medium. The log now carries the player's comments where they were made. Position is the feature: a remark like "why did that do nothing?" is about the move above it, and collected at the bottom it is a sentence with no subject. round/step cannot order a note against the log because several commands share a step, so the server records how many commands had been played — which it knows exactly — and the page places it there. A comment must not be readable as something the game did. The log is the recorder's vocabulary; notes render in their own block, attributed to the player, quoted. The .note CSS already existed and nothing had ever used it. No column was added to the trial log. trials.py skipped any row that was not five cells, silently, so a sixth column would have made `make trials` report zero notes for every log at once. That latent defect is fixed on its own terms: a wrong column count now raises, and the walk reports the real reason rather than blaming a missing block for every failure. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
499d9fe3d7
commit
4ddff3b17c
8 changed files with 455 additions and 26 deletions
|
|
@ -47,6 +47,14 @@ pub struct Server {
|
|||
#[derive(Debug, Clone)]
|
||||
pub struct TrialNote {
|
||||
pub n: usize,
|
||||
/// Commands played when this was written (CB-WP-0032), so the page
|
||||
/// can put the comment where it was made. **Held in memory only.**
|
||||
/// It is deliberately NOT a column in the trial log: `trials.py`
|
||||
/// skips any row that is not five cells, silently, so a sixth column
|
||||
/// would make `make trials` report zero notes for a file full of
|
||||
/// them — the exact failure its own docstring says it exists to
|
||||
/// prevent.
|
||||
pub after: usize,
|
||||
pub round: u8,
|
||||
pub step: String,
|
||||
pub state_hash: String,
|
||||
|
|
@ -146,10 +154,12 @@ impl Server {
|
|||
return Err("no trial log for this session — start with --trial".into());
|
||||
};
|
||||
let hash = cb_events::state_hash_hex(state);
|
||||
let after = self.journal.borrow().len();
|
||||
let mut log = self.notes.borrow_mut();
|
||||
let n = log.len() + 1;
|
||||
log.push(TrialNote {
|
||||
n,
|
||||
after,
|
||||
round: state.round,
|
||||
step: step.to_string(),
|
||||
state_hash: hash[..12].to_string(),
|
||||
|
|
@ -173,6 +183,20 @@ impl Server {
|
|||
self.journal.clone()
|
||||
}
|
||||
|
||||
/// The player's own comments, positioned for the log (CB-WP-0032).
|
||||
fn log_notes(&self) -> Vec<cb_render_html::doc::LogNote> {
|
||||
self.notes
|
||||
.borrow()
|
||||
.iter()
|
||||
.map(|n| cb_render_html::doc::LogNote {
|
||||
after: n.after,
|
||||
round: n.round,
|
||||
step: n.step.clone(),
|
||||
text: n.text.clone(),
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// The log as the page renders it, in the recorder's vocabulary.
|
||||
///
|
||||
/// `record::to_step` is reused rather than phrased afresh: what the
|
||||
|
|
@ -261,7 +285,10 @@ impl Server {
|
|||
},
|
||||
Some(seat),
|
||||
may_pass,
|
||||
&self.log_lines(),
|
||||
cb_render_html::doc::Account {
|
||||
lines: &self.log_lines(),
|
||||
notes: &self.log_notes(),
|
||||
},
|
||||
// CB-WP-0027 T02: the meta panel's own content.
|
||||
// The running tally lives with the driver, so the
|
||||
// server is handed the lines rather than the state.
|
||||
|
|
@ -394,7 +421,10 @@ impl Server {
|
|||
message,
|
||||
&self.guard.endpoint(),
|
||||
note_to.as_deref(),
|
||||
&self.log_lines(),
|
||||
cb_render_html::doc::Account {
|
||||
lines: &self.log_lines(),
|
||||
notes: &self.log_notes(),
|
||||
},
|
||||
&series_lines(tally),
|
||||
);
|
||||
respond(&mut stream, 200, "text/html; charset=utf-8", &page);
|
||||
|
|
@ -875,6 +905,8 @@ mod tests {
|
|||
// 22-byte body is a test that fails for a reason having
|
||||
// nothing to do with the feature.
|
||||
post(&token, "/note", "note=so+that+is+why+it"),
|
||||
// CB-WP-0032: and it comes BACK, in the log.
|
||||
format!("GET /?t={token} HTTP/1.1\r\nHost: 127.0.0.1:{port}\r\n\r\n"),
|
||||
post(&token, "/command", "down=done&up=done"),
|
||||
],
|
||||
);
|
||||
|
|
@ -906,9 +938,20 @@ mod tests {
|
|||
"the redirect dropped the token, which reads as a refusal: {}",
|
||||
replies[1]
|
||||
);
|
||||
// CB-WP-0032. Written, then read back on the very next page —
|
||||
// the round trip, not just the write.
|
||||
assert!(
|
||||
replies[2].contains("so that is why it"),
|
||||
"the note was saved but never shown back: {}",
|
||||
replies[2]
|
||||
);
|
||||
assert!(
|
||||
replies[2].contains("class=\"note\""),
|
||||
"the note was shown without marking it as the player's"
|
||||
);
|
||||
// The session survived the note.
|
||||
assert!(
|
||||
replies[2].contains("closed"),
|
||||
replies[3].contains("closed"),
|
||||
"the note ended the session, so nothing could follow it: {}",
|
||||
replies[2]
|
||||
);
|
||||
|
|
@ -932,7 +975,14 @@ mod tests {
|
|||
#[test]
|
||||
fn a_game_that_stopped_offers_no_comment_box() {
|
||||
let mut s = String::new();
|
||||
let page = cb_render_html::doc::ending(None, "it broke", "/c", None, &[], &[]);
|
||||
let page = cb_render_html::doc::ending(
|
||||
None,
|
||||
"it broke",
|
||||
"/c",
|
||||
None,
|
||||
cb_render_html::doc::Account::of(&[]),
|
||||
&[],
|
||||
);
|
||||
s.push_str(&page);
|
||||
assert!(
|
||||
!s.contains("id=\"cb-note\""),
|
||||
|
|
@ -1013,7 +1063,7 @@ mod tests {
|
|||
"P1 ran out of input",
|
||||
"/command?t=x",
|
||||
None,
|
||||
&[],
|
||||
cb_render_html::doc::Account::of(&[]),
|
||||
&[],
|
||||
);
|
||||
assert!(
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue