Compare commits
4 commits
a9754d90ed
...
347f356b1d
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
347f356b1d | ||
|
|
f5a47490f7 | ||
|
|
4f8859527d | ||
|
|
e2c6452e4f |
10 changed files with 102 additions and 32 deletions
|
|
@ -8,6 +8,7 @@ on:
|
||||||
branches: ['main']
|
branches: ['main']
|
||||||
paths:
|
paths:
|
||||||
- 'bakery/**'
|
- 'bakery/**'
|
||||||
|
- 'bread-utils/**'
|
||||||
- 'Cargo.toml'
|
- 'Cargo.toml'
|
||||||
- 'Cargo.lock'
|
- 'Cargo.lock'
|
||||||
- '.forgejo/workflows/dev-bakery.yml'
|
- '.forgejo/workflows/dev-bakery.yml'
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,9 @@ on:
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
package:
|
package:
|
||||||
|
# PKGBUILD pkgver cannot contain `-`; skip RC tags the same way
|
||||||
|
# release-bakery.yml does.
|
||||||
|
if: ${{ !contains(github.ref_name, '-rc.') }}
|
||||||
runs-on: [self-hosted, hestia]
|
runs-on: [self-hosted, hestia]
|
||||||
container:
|
container:
|
||||||
image: archlinux:latest
|
image: archlinux:latest
|
||||||
|
|
|
||||||
|
|
@ -7,11 +7,9 @@ name: beta (rc) bakery
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
tags: ['v*']
|
tags: ['v*']
|
||||||
paths:
|
# No paths: filter. Tag pushes compare against an unrelated commit and
|
||||||
- 'bakery/**'
|
# would skip the RC publish if bakery/** wasn't in that diff; the job
|
||||||
- 'Cargo.toml'
|
# `if: contains -rc.` is the real gate.
|
||||||
- 'Cargo.lock'
|
|
||||||
- '.forgejo/workflows/beta-bakery.yml'
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
|
|
@ -35,6 +33,7 @@ jobs:
|
||||||
run: |
|
run: |
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
VERSION="${GITHUB_REF_NAME#v}"
|
VERSION="${GITHUB_REF_NAME#v}"
|
||||||
|
echo "VERSION=${VERSION}" >> "$GITHUB_ENV"
|
||||||
PKG_DIR="/srv/breadway-dl/beta/bakery/${VERSION}"
|
PKG_DIR="/srv/breadway-dl/beta/bakery/${VERSION}"
|
||||||
mkdir -p "${PKG_DIR}"
|
mkdir -p "${PKG_DIR}"
|
||||||
cp "src/target/release/bakery" "${PKG_DIR}/bakery-x86_64"
|
cp "src/target/release/bakery" "${PKG_DIR}/bakery-x86_64"
|
||||||
|
|
|
||||||
|
|
@ -7,11 +7,9 @@ name: beta (rc) bread-theme
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
tags: ['v*']
|
tags: ['v*']
|
||||||
paths:
|
# No paths: filter. Tag pushes compare against an unrelated commit and
|
||||||
- 'bread-theme/**'
|
# would skip the RC publish if bread-theme/** wasn't in that diff; the
|
||||||
- 'Cargo.toml'
|
# job `if: contains -rc.` is the real gate.
|
||||||
- 'Cargo.lock'
|
|
||||||
- '.forgejo/workflows/beta-bread-theme.yml'
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
|
|
@ -32,6 +30,7 @@ jobs:
|
||||||
run: |
|
run: |
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
VERSION="${GITHUB_REF_NAME#v}"
|
VERSION="${GITHUB_REF_NAME#v}"
|
||||||
|
echo "VERSION=${VERSION}" >> "$GITHUB_ENV"
|
||||||
PKG_DIR="/srv/breadway-dl/beta/bread-theme/${VERSION}"
|
PKG_DIR="/srv/breadway-dl/beta/bread-theme/${VERSION}"
|
||||||
mkdir -p "${PKG_DIR}"
|
mkdir -p "${PKG_DIR}"
|
||||||
cp "src/target/release/bread-theme" "${PKG_DIR}/bread-theme-x86_64"
|
cp "src/target/release/bread-theme" "${PKG_DIR}/bread-theme-x86_64"
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,9 @@ are `bakery` (the ecosystem package manager) and `bread-theme` (the shared
|
||||||
theming crate). Shared crates that sibling apps pin — not bakery packages
|
theming crate). Shared crates that sibling apps pin — not bakery packages
|
||||||
of their own — are `bread-utils`, `bread-app`, `bread-onnx`,
|
of their own — are `bread-utils`, `bread-app`, `bread-onnx`,
|
||||||
`bread-screenshots`, and `bread-capture`. `bread-polkit` is an in-tree
|
`bread-screenshots`, and `bread-capture`. `bread-polkit` is an in-tree
|
||||||
session agent, also not a bakery product. Other ecosystem products
|
session agent: it has `bread-polkit/bakery.toml` so it *can* be published,
|
||||||
|
but it is not in `registry/bread-ecosystem.toml` (unpublished — not on
|
||||||
|
the bakery index, not on the BOS ISO). Other ecosystem products
|
||||||
(`bread`, `breadbar`, `breadbox`, …) live in their own repos under
|
(`bread`, `breadbar`, `breadbox`, …) live in their own repos under
|
||||||
`Breadway/` but follow the same workflow described here. The product list
|
`Breadway/` but follow the same workflow described here. The product list
|
||||||
is `registry/bread-ecosystem.toml`. New GTK tools should depend on
|
is `registry/bread-ecosystem.toml`. New GTK tools should depend on
|
||||||
|
|
|
||||||
50
Cargo.lock
generated
50
Cargo.lock
generated
|
|
@ -145,7 +145,7 @@ dependencies = [
|
||||||
"chrono",
|
"chrono",
|
||||||
"clap",
|
"clap",
|
||||||
"clap_complete",
|
"clap_complete",
|
||||||
"dirs",
|
"dirs 5.0.1",
|
||||||
"fs4",
|
"fs4",
|
||||||
"hex",
|
"hex",
|
||||||
"minisign-verify",
|
"minisign-verify",
|
||||||
|
|
@ -243,20 +243,21 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bread-shared"
|
name = "bread-shared"
|
||||||
version = "0.7.0"
|
version = "0.8.0"
|
||||||
source = "git+https://git.breadway.dev/Breadway/bread?tag=v0.7.0#22e34e2cf2202305d7960759dfccb54dc79f948b"
|
source = "git+https://git.breadway.dev/Breadway/bread?tag=v0.8.0#cdd5de8f58e437b3fc6d9b9087eb7b3d0fd09704"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"dirs",
|
"dirs 6.0.0",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"toml 0.8.23",
|
"toml 0.8.23",
|
||||||
|
"uuid",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bread-theme"
|
name = "bread-theme"
|
||||||
version = "0.7.4"
|
version = "0.7.4"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"dirs",
|
"dirs 5.0.1",
|
||||||
"gtk4",
|
"gtk4",
|
||||||
"libadwaita",
|
"libadwaita",
|
||||||
"serde",
|
"serde",
|
||||||
|
|
@ -268,7 +269,7 @@ name = "bread-utils"
|
||||||
version = "0.7.4"
|
version = "0.7.4"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bread-shared",
|
"bread-shared",
|
||||||
"dirs",
|
"dirs 5.0.1",
|
||||||
"gtk4",
|
"gtk4",
|
||||||
"gtk4-layer-shell",
|
"gtk4-layer-shell",
|
||||||
"serde",
|
"serde",
|
||||||
|
|
@ -610,7 +611,16 @@ version = "5.0.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "44c45a9d03d6676652bcb5e724c7e988de1acad23a711b5217ab9cbecbec2225"
|
checksum = "44c45a9d03d6676652bcb5e724c7e988de1acad23a711b5217ab9cbecbec2225"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"dirs-sys",
|
"dirs-sys 0.4.1",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "dirs"
|
||||||
|
version = "6.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c3e8aa94d75141228480295a7d0e7feb620b1a5ad9f12bc40be62411e38cce4e"
|
||||||
|
dependencies = [
|
||||||
|
"dirs-sys 0.5.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -621,10 +631,22 @@ checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
"option-ext",
|
"option-ext",
|
||||||
"redox_users",
|
"redox_users 0.4.6",
|
||||||
"windows-sys 0.48.0",
|
"windows-sys 0.48.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "dirs-sys"
|
||||||
|
version = "0.5.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e01a3366d27ee9890022452ee61b2b63a67e6f13f58900b651ff5665f0bb1fab"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"option-ext",
|
||||||
|
"redox_users 0.5.2",
|
||||||
|
"windows-sys 0.61.2",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "displaydoc"
|
name = "displaydoc"
|
||||||
version = "0.2.6"
|
version = "0.2.6"
|
||||||
|
|
@ -1966,6 +1988,17 @@ dependencies = [
|
||||||
"thiserror 1.0.69",
|
"thiserror 1.0.69",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "redox_users"
|
||||||
|
version = "0.5.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a4e608c6638b9c18977b00b475ac1f28d14e84b27d8d42f70e0bf1e3dec127ac"
|
||||||
|
dependencies = [
|
||||||
|
"getrandom 0.2.17",
|
||||||
|
"libredox",
|
||||||
|
"thiserror 2.0.18",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "regex"
|
name = "regex"
|
||||||
version = "1.13.1"
|
version = "1.13.1"
|
||||||
|
|
@ -2703,6 +2736,7 @@ version = "1.24.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "2cefc03fd367c0c6d4305de1b312cf00248c4114f4a0418ce6a6af769e3b0bd9"
|
checksum = "2cefc03fd367c0c6d4305de1b312cf00248c4114f4a0418ce6a6af769e3b0bd9"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"getrandom 0.4.3",
|
||||||
"js-sys",
|
"js-sys",
|
||||||
"serde_core",
|
"serde_core",
|
||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
|
|
|
||||||
|
|
@ -153,7 +153,7 @@ bread-ecosystem/
|
||||||
├── bread-theme/ # shared pywal + fixed-dark-base theming crate
|
├── bread-theme/ # shared pywal + fixed-dark-base theming crate
|
||||||
├── bread-utils/ # shared plumbing (Hyprland IPC, singleton, XDG, BreadClient, …)
|
├── bread-utils/ # shared plumbing (Hyprland IPC, singleton, XDG, BreadClient, …)
|
||||||
├── bread-app/ # GTK bootstrap new tools should use (app id, singleton, overlay, command listen)
|
├── bread-app/ # GTK bootstrap new tools should use (app id, singleton, overlay, command listen)
|
||||||
├── bread-polkit/ # themed PolicyKit authentication agent (not a bakery product)
|
├── bread-polkit/ # themed PolicyKit agent (bakery.toml present; unpublished)
|
||||||
├── bread-onnx/ # shared ONNX runtime helpers
|
├── bread-onnx/ # shared ONNX runtime helpers
|
||||||
├── bread-screenshots/ # grim capture primitive used by app `--screenshot` modes
|
├── bread-screenshots/ # grim capture primitive used by app `--screenshot` modes
|
||||||
├── bread-capture/ # orchestrator that drives those `--screenshot` modes
|
├── bread-capture/ # orchestrator that drives those `--screenshot` modes
|
||||||
|
|
@ -179,8 +179,10 @@ tree; `bread-polkit` is the first in-tree consumer.
|
||||||
### bread-polkit
|
### bread-polkit
|
||||||
|
|
||||||
A session PolicyKit authentication agent (password prompt, cancel,
|
A session PolicyKit authentication agent (password prompt, cancel,
|
||||||
identity). Not a wrapper around `polkit-gnome`. Not published via bakery
|
identity). Not a wrapper around `polkit-gnome`. `bread-polkit/bakery.toml`
|
||||||
and not on the BOS ISO lockfile.
|
exists so it can be published via bakery; it is not in
|
||||||
|
`registry/bread-ecosystem.toml` and is therefore unpublished — not on the
|
||||||
|
bakery index and not on the BOS ISO lockfile.
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
cargo run -p bread-polkit
|
cargo run -p bread-polkit
|
||||||
|
|
|
||||||
11
bread-polkit/bakery.toml
Normal file
11
bread-polkit/bakery.toml
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
name = "bread-polkit"
|
||||||
|
description = "Themed PolicyKit authentication agent for the bread desktop"
|
||||||
|
binaries = ["bread-polkit"]
|
||||||
|
system_deps = ["gtk4", "gtk4-layer-shell", "polkit"]
|
||||||
|
optional_system_deps = ["hyprland"]
|
||||||
|
bread_deps = []
|
||||||
|
license_file = "LICENSE"
|
||||||
|
desktop_file = "bread-polkit.desktop"
|
||||||
|
|
||||||
|
[install]
|
||||||
|
post_install = []
|
||||||
|
|
@ -32,6 +32,13 @@ pub mod tokens {
|
||||||
pub const RADIUS_PILL: u16 = 999;
|
pub const RADIUS_PILL: u16 = 999;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// CSS `font-family` list: quote the named face, leave the generic fallback
|
||||||
|
/// unquoted. Wrapping [`tokens::FONT_FAMILY`] in one pair of quotes would
|
||||||
|
/// make a single family named "Varela Round, sans-serif" and drop sans-serif.
|
||||||
|
fn css_font_family() -> &'static str {
|
||||||
|
"'Varela Round', sans-serif"
|
||||||
|
}
|
||||||
|
|
||||||
/// Emit the `@define-color` block that all bread apps use, plus the shared
|
/// Emit the `@define-color` block that all bread apps use, plus the shared
|
||||||
/// font rule.
|
/// font rule.
|
||||||
///
|
///
|
||||||
|
|
@ -47,9 +54,9 @@ pub mod tokens {
|
||||||
/// one color-block implementation and it cannot drift again.
|
/// one color-block implementation and it cannot drift again.
|
||||||
pub fn css_vars(p: &Palette) -> String {
|
pub fn css_vars(p: &Palette) -> String {
|
||||||
format!(
|
format!(
|
||||||
"{vars}* {{ font-family: '{font}'; font-size: {size}px; }}\n",
|
"{vars}* {{ font-family: {font}; font-size: {size}px; }}\n",
|
||||||
vars = define_colors(p),
|
vars = define_colors(p),
|
||||||
font = tokens::FONT_FAMILY,
|
font = css_font_family(),
|
||||||
size = tokens::FONT_SIZE_BASE,
|
size = tokens::FONT_SIZE_BASE,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -144,7 +151,7 @@ pub fn css_tokens() -> String {
|
||||||
use tokens::*;
|
use tokens::*;
|
||||||
format!(
|
format!(
|
||||||
":root {{\n\
|
":root {{\n\
|
||||||
\x20\x20--font-family: '{font}';\n\
|
\x20\x20--font-family: {font};\n\
|
||||||
\x20\x20--font-size-base: {base}px;\n\
|
\x20\x20--font-size-base: {base}px;\n\
|
||||||
\x20\x20--font-size-secondary: {sec}px;\n\
|
\x20\x20--font-size-secondary: {sec}px;\n\
|
||||||
\x20\x20--space-xs: {xs}px;\n\
|
\x20\x20--space-xs: {xs}px;\n\
|
||||||
|
|
@ -157,7 +164,7 @@ pub fn css_tokens() -> String {
|
||||||
\x20\x20--radius-tertiary: {r3}px;\n\
|
\x20\x20--radius-tertiary: {r3}px;\n\
|
||||||
\x20\x20--radius-pill: {pill}px;\n\
|
\x20\x20--radius-pill: {pill}px;\n\
|
||||||
}}\n",
|
}}\n",
|
||||||
font = FONT_FAMILY,
|
font = css_font_family(),
|
||||||
base = FONT_SIZE_BASE,
|
base = FONT_SIZE_BASE,
|
||||||
sec = FONT_SIZE_SECONDARY,
|
sec = FONT_SIZE_SECONDARY,
|
||||||
xs = SPACE_XS,
|
xs = SPACE_XS,
|
||||||
|
|
@ -182,7 +189,7 @@ pub fn stylesheet(p: &Palette) -> String {
|
||||||
use tokens::*;
|
use tokens::*;
|
||||||
format!(
|
format!(
|
||||||
"{vars}\
|
"{vars}\
|
||||||
* {{ font-family: '{font}'; font-size: {base}px; }}\n\
|
* {{ font-family: {font}; font-size: {base}px; }}\n\
|
||||||
/* Colour is set on containers; labels inherit it, so text on any panel,\
|
/* Colour is set on containers; labels inherit it, so text on any panel,\
|
||||||
button, or accent is always the legible ink for that background. Bare\
|
button, or accent is always the legible ink for that background. Bare\
|
||||||
`label {{ color }}` is deliberately avoided — as a type selector it\
|
`label {{ color }}` is deliberately avoided — as a type selector it\
|
||||||
|
|
@ -265,7 +272,7 @@ pub fn stylesheet(p: &Palette) -> String {
|
||||||
textview, .mono {{ font-family: monospace; }}\n\
|
textview, .mono {{ font-family: monospace; }}\n\
|
||||||
textview text {{ background-color: @surface; color: @on-surface; }}\n",
|
textview text {{ background-color: @surface; color: @on-surface; }}\n",
|
||||||
vars = define_colors(p),
|
vars = define_colors(p),
|
||||||
font = FONT_FAMILY,
|
font = css_font_family(),
|
||||||
base = FONT_SIZE_BASE,
|
base = FONT_SIZE_BASE,
|
||||||
sec = FONT_SIZE_SECONDARY,
|
sec = FONT_SIZE_SECONDARY,
|
||||||
xs = SPACE_XS, sm = SPACE_SM, md = SPACE_MD, lg = SPACE_LG,
|
xs = SPACE_XS, sm = SPACE_SM, md = SPACE_MD, lg = SPACE_LG,
|
||||||
|
|
@ -342,7 +349,11 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn css_vars_contains_font_rule() {
|
fn css_vars_contains_font_rule() {
|
||||||
let css = css_vars(&Palette::default());
|
let css = css_vars(&Palette::default());
|
||||||
assert!(css.contains("Varela Round"));
|
assert!(css.contains("font-family: 'Varela Round', sans-serif;"));
|
||||||
|
assert!(
|
||||||
|
!css.contains("font-family: 'Varela Round, sans-serif'"),
|
||||||
|
"named face and generic fallback must not be one quoted family"
|
||||||
|
);
|
||||||
assert!(css.contains("14px"));
|
assert!(css.contains("14px"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -413,7 +424,11 @@ mod tests {
|
||||||
] {
|
] {
|
||||||
assert!(css.contains(sel), "stylesheet missing selector: {sel}");
|
assert!(css.contains(sel), "stylesheet missing selector: {sel}");
|
||||||
}
|
}
|
||||||
assert!(css.contains("Varela Round"));
|
assert!(css.contains("font-family: 'Varela Round', sans-serif;"));
|
||||||
|
assert!(
|
||||||
|
!css.contains("font-family: 'Varela Round, sans-serif'"),
|
||||||
|
"named face and generic fallback must not be one quoted family"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
@ -445,7 +460,11 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn css_tokens_contains_font_and_spacing_vars() {
|
fn css_tokens_contains_font_and_spacing_vars() {
|
||||||
let css = css_tokens();
|
let css = css_tokens();
|
||||||
assert!(css.contains("--font-family: 'Varela Round, sans-serif';"));
|
assert!(css.contains("--font-family: 'Varela Round', sans-serif;"));
|
||||||
|
assert!(
|
||||||
|
!css.contains("--font-family: 'Varela Round, sans-serif'"),
|
||||||
|
"named face and generic fallback must not be one quoted family"
|
||||||
|
);
|
||||||
assert!(css.contains("--font-size-base: 14px;"));
|
assert!(css.contains("--font-size-base: 14px;"));
|
||||||
assert!(css.contains("--space-md: 12px;"));
|
assert!(css.contains("--space-md: 12px;"));
|
||||||
assert!(css.contains("--radius-pill: 999px;"));
|
assert!(css.contains("--radius-pill: 999px;"));
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@ dirs = { workspace = true }
|
||||||
gtk4 = { version = "0.11", features = ["v4_12"], optional = true }
|
gtk4 = { version = "0.11", features = ["v4_12"], optional = true }
|
||||||
gtk4-layer-shell = { version = "0.8", optional = true }
|
gtk4-layer-shell = { version = "0.8", optional = true }
|
||||||
toml_edit = { version = "0.22", optional = true }
|
toml_edit = { version = "0.22", optional = true }
|
||||||
bread-shared = { git = "https://git.breadway.dev/Breadway/bread", tag = "v0.7.0", optional = true }
|
bread-shared = { git = "https://git.breadway.dev/Breadway/bread", tag = "v0.8.0", optional = true }
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
# Enable the layer-shell popup scaffold (breadbox, breadclip). Kept optional
|
# Enable the layer-shell popup scaffold (breadbox, breadclip). Kept optional
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue