Sitelet https://github.com/Ameyanagi/aibo/commit/3a7c68b15b02b598002047f271df3f97daf362c9
Skip to content

Commit 3a7c68b

Browse files
authored
Merge pull request #12 from Ameyanagi/notice-in-panel
Notices in the panel; fix the dev release publish
2 parents 7ff4bd9 + 2b0ebbb commit 3a7c68b

6 files changed

Lines changed: 121 additions & 28 deletions

File tree

‎.githooks/pre-commit‎

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
#!/bin/sh
2+
# The gates CI enforces, run before the commit exists — with the *same* flags.
3+
#
4+
# The flags are the point. A bare `cargo clippy` reports `missing_docs` and
5+
# friends as warnings and exits 0, while CI runs `-D warnings` and fails; that
6+
# gap has cost this repository several red builds whose fix was a doc comment.
7+
# So this mirrors `.github/workflows/ci.yml` exactly rather than approximating
8+
# it. Tests are left to pre-push: they take minutes, and a commit should not.
9+
#
10+
# Install: git config core.hooksPath .githooks
11+
# Skip once (WIP commit): git commit --no-verify
12+
13+
set -e
14+
15+
if ! command -v cargo >/dev/null 2>&1; then
16+
echo "pre-commit: cargo not on PATH, skipping the Rust gates" >&2
17+
exit 0
18+
fi
19+
20+
echo "pre-commit: cargo fmt --check"
21+
cargo fmt --all -- --check
22+
23+
echo "pre-commit: cargo clippy -D warnings"
24+
cargo clippy --workspace --all-features --all-targets --locked -- -D warnings -A unsafe-code

‎.githooks/pre-push‎

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
#!/bin/sh
2+
# The test suite, before it can reach a pull request.
3+
#
4+
# Separate from pre-commit because it is slow: commits stay cheap, and the
5+
# expensive gate runs once per push instead of once per commit.
6+
#
7+
# Install: git config core.hooksPath .githooks
8+
# Skip once: git push --no-verify
9+
10+
set -e
11+
12+
# Preserve the git-lfs hook this replaces, when lfs is in use.
13+
if command -v git-lfs >/dev/null 2>&1; then
14+
git lfs pre-push "$@"
15+
fi
16+
17+
if ! command -v cargo >/dev/null 2>&1; then
18+
echo "pre-push: cargo not on PATH, skipping the tests" >&2
19+
exit 0
20+
fi
21+
22+
echo "pre-push: cargo test --workspace"
23+
cargo test --workspace --all-targets --locked

‎.github/workflows/release.yml‎

Lines changed: 20 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -416,23 +416,27 @@ jobs:
416416
if [ "$PRERELEASE" = "true" ]; then
417417
flags+=(--prerelease)
418418
fi
419-
if [ "$TAG" = "dev" ]; then
420-
# Only the rolling `dev` release needs `--target`: its tag is
421-
# deleted above and must be recut at this run's commit. For a
422-
# pushed tag the commit is already pinned — and passing `--target`
423-
# there trips a GITHUB_TOKEN limitation (cli/cli#9514: HTTP 403
424-
# "Resource not accessible by integration" despite
425-
# `contents: write`; the same call works with user credentials).
426-
flags+=(--target "${{ github.sha }}")
427-
fi
419+
# No `--target` anywhere. It trips a GITHUB_TOKEN limitation
420+
# (cli/cli#9514: HTTP 403 "Resource not accessible by integration"
421+
# despite `contents: write`; the same call works with user
422+
# credentials), and it is not needed: a pushed tag already pins its
423+
# commit, and `dev` is recut below by pushing its tag with the
424+
# checkout's own credentials before the release is created.
428425
429-
if [ "$TAG" = "dev" ] && gh release view "$TAG" >/dev/null 2>&1; then
430-
# Deleted and recreated rather than edited, because `gh release edit`
431-
# cannot move a tag: editing in place would leave `dev` pointing at
432-
# whatever commit it was first cut from while serving assets built
433-
# from a newer one. The URL is unchanged either way — it is keyed on
434-
# the tag name, not the release id.
435-
gh release delete "$TAG" --yes --cleanup-tag
426+
if [ "$TAG" = "dev" ]; then
427+
# Deleted and recreated rather than edited, because `gh release
428+
# edit` cannot move a tag: editing in place would leave `dev`
429+
# pointing at whatever commit it was first cut from while serving
430+
# assets built from a newer one. The URL is unchanged either way —
431+
# it is keyed on the tag name, not the release id.
432+
if gh release view "$TAG" >/dev/null 2>&1; then
433+
gh release delete "$TAG" --yes --cleanup-tag
434+
fi
435+
# Move the tag with git rather than with `--target`: the checkout
436+
# action's credentials can push a tag, and the release then simply
437+
# attaches to a tag that already points at this run's commit.
438+
git tag -f "$TAG" "${{ github.sha }}"
439+
git push -f origin "refs/tags/$TAG"
436440
fi
437441
438442
if gh release view "$TAG" >/dev/null 2>&1; then

‎README.md‎

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,17 @@ cargo clippy --workspace --all-targets -- -D warnings
118118
cargo fmt --all -- --check
119119
```
120120

121+
Run them before pushing by installing the repository's hooks once:
122+
123+
```sh
124+
git config core.hooksPath .githooks
125+
```
126+
127+
`pre-commit` runs fmt and clippy with **CI's exact flags** — a bare
128+
`cargo clippy` leaves `missing_docs` a warning and exits 0, which is how a
129+
green local check becomes a red build — and `pre-push` runs the tests.
130+
`--no-verify` skips either when a work-in-progress commit needs to exist.
131+
121132
`vendor/` carries temporary patches to `cosmic-text` and `cryoglyph`
122133
(overflow fixes for CJK fallback shaping) applied via `[patch.crates-io]`;
123134
they retire with the next iced upgrade.

‎crates/aibo-ui/src/panel.rs‎

Lines changed: 26 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1476,6 +1476,13 @@ impl PanelState {
14761476
// Wrapped composer lines add height in every phase; see
14771477
// `input_extra_height`.
14781478
let input_extra = self.input_extra_height();
1479+
// A notice is now a row in the panel rather than a floating box, so
1480+
// it costs height like every other row.
1481+
let notice = if self.toast.is_some() {
1482+
NOTICE_ROW_HEIGHT
1483+
} else {
1484+
0.0
1485+
};
14791486
if self.has_conversation() {
14801487
// `CHAT_ESTIMATE_SURPLUS`: the collapsed baseline and the footer
14811488
// estimate both over-count the chat composition slightly —
@@ -1486,6 +1493,7 @@ impl PanelState {
14861493
+ attachments
14871494
+ selection
14881495
+ input_extra
1496+
+ notice
14891497
+ self.transcript_height()
14901498
+ self.chat_error_height()
14911499
+ self.footer_height()
@@ -1495,7 +1503,7 @@ impl PanelState {
14951503

14961504
match self.phase {
14971505
Phase::Hidden | Phase::WarmingUp { .. } | Phase::Idle => {
1498-
(theme::PANEL_HEIGHT_COLLAPSED + attachments + selection + input_extra)
1506+
(theme::PANEL_HEIGHT_COLLAPSED + attachments + selection + input_extra + notice)
14991507
.min(self.max_panel_height())
15001508
}
15011509
// `COLLAPSED` is input-plus-chrome only. Everything `footer()`
@@ -1507,6 +1515,7 @@ impl PanelState {
15071515
+ attachments
15081516
+ selection
15091517
+ input_extra
1518+
+ notice
15101519
+ self.answer_height()
15111520
+ self.footer_height())
15121521
.min(self.max_panel_height()),
@@ -1737,9 +1746,10 @@ pub fn view(state: &PanelState, appearance: theme::Appearance) -> Element<'_, Me
17371746
body = body.push(widgets::railed(RailState::Inactive, footer(state)));
17381747
}
17391748

1740-
body = body.push(widgets::railed(input_rail_state(state), input_row(state)));
1741-
1742-
let mut stack = column![body].spacing(space(2.0));
1749+
// Notices live *in* the panel, on the rail, directly above the composer —
1750+
// not floating over it (owner ruling, 2026-08-02: "can we just show it in
1751+
// the chat window?"). A floating box overlapped the conversation and,
1752+
// being outside the layout, could not be sized against it.
17431753
if let Some(toast) = &state.toast {
17441754
let action = if toast.offer_diagnostics {
17451755
Action::new(
@@ -1750,9 +1760,16 @@ pub fn view(state: &PanelState, appearance: theme::Appearance) -> Element<'_, Me
17501760
} else {
17511761
Action::new(Key::ActionDismiss, "esc", Message::DismissToast)
17521762
};
1753-
stack = stack.push(widgets::toast(toast.severity, &toast.body, Some(action)));
1763+
body = body.push(widgets::railed(
1764+
RailState::Alert,
1765+
widgets::toast(toast.severity, &toast.body, Some(action)),
1766+
));
17541767
}
17551768

1769+
body = body.push(widgets::railed(input_rail_state(state), input_row(state)));
1770+
1771+
let stack = column![body].spacing(space(2.0));
1772+
17561773
// The quick-pick and the `@` finder float over the panel as menus
17571774
// (t3-style) instead of replacing the body: the panel stays where it was,
17581775
// the menu arrives and leaves, and mid-conversation the window does not
@@ -3270,6 +3287,10 @@ const PICKER_PANEL_HEIGHT: f32 = 500.0;
32703287
const PICKER_MENU_WIDTH: f32 = 560.0;
32713288
/// The running activity card: header row plus a three-row scrolling interior.
32723289
/// Fixed so a streaming run never resizes the window (§16's reserve rule).
3290+
/// One notice row above the composer: bar-and-text plus the toast's own
3291+
/// padding and the column's spacing.
3292+
const NOTICE_ROW_HEIGHT: f32 = 52.0;
3293+
32733294
const TASK_CARD_RUNNING_HEIGHT: f32 = 148.0;
32743295
/// The settled card: one summary line in a quiet container.
32753296
const TASK_CARD_DONE_HEIGHT: f32 = 34.0;

‎crates/aibo-ui/src/widgets.rs‎

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -744,10 +744,15 @@ pub fn footnote<'a, Message: 'a>(body: String) -> Element<'a, Message> {
744744
.into()
745745
}
746746

747-
/// A non-blocking toast (§13: `InsertFailed`, `CaptureFailed`).
747+
/// The severity bar's height: one line of [`type_scale::META`] with its
748+
/// leading. A constant rather than `Length::Fill` — see the note in [`toast`].
749+
const TOAST_BAR_HEIGHT: f32 = 18.0;
750+
751+
/// A non-blocking notice (§13: `InsertFailed`, `CaptureFailed`).
748752
///
749-
/// The result stays in the panel behind it so the user can copy manually — the
750-
/// toast never replaces the content it is complaining about.
753+
/// Rendered as a row of the panel, above the composer — the result stays
754+
/// visible beside it so the user can still copy manually, and a notice never
755+
/// replaces the content it is complaining about.
751756
pub fn toast<'a, Message: Clone + 'a>(
752757
severity: Severity,
753758
body: &str,
@@ -758,12 +763,17 @@ pub fn toast<'a, Message: Clone + 'a>(
758763
// toast is deliberately quiet chrome (§13, non-blocking) and tinting a
759764
// whole sentence red would make `InsertFailed` louder than the answer
760765
// it is sitting beside.
761-
container(Space::new().width(2.0).height(Length::Fill)).style(move |t: &iced::Theme| {
762-
container::Style {
766+
// Fixed height, NOT `Length::Fill`. A cross-axis `Fill` makes the row
767+
// claim every point the parent can spare, and the toast lives in a
768+
// column under a `Length::Fill` panel — so one line of text rendered
769+
// as a box the height of the whole window with the sentence stranded
770+
// at the bottom (owner screenshot, 2026-08-02).
771+
container(Space::new().width(2.0).height(TOAST_BAR_HEIGHT)).style(
772+
move |t: &iced::Theme| container::Style {
763773
background: Some(Background::Color(severity.color(&theme::palette_of(t)))),
764774
..Default::default()
765-
}
766-
}),
775+
},
776+
),
767777
text(body.to_owned())
768778
.size(type_scale::META)
769779
.style(theme::text_primary),

0 commit comments

Comments
 (0)