refactor(theme): remove redundant applied theme mirror
- commit
- d4234db1b8cd7c314d6c23d587fba580f099926c
- author
- brice · · 2026-07-18 08:40 UTC
- parent
- 810e9f1
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email
4 files · +19 -23
M internal/usecase/daemon/client.go +8 -12
| @@ -57,7 +57,6 @@ type attachedClient struct { | |||
| 57 | 57 | sess Guarded[*session] | |
| 58 | 58 | mouseScan mouse.Scanner | |
| 59 | 59 | themeMu sync.Mutex | |
| 60 | - | theme themeui.Theme // compatibility mirror of applied.Raw | |
| 61 | 60 | clientTheme themeui.Theme | |
| 62 | 61 | appliedTheme appliedTheme | |
| 63 | 62 | lastCursor cursorOut | |
| @@ -147,19 +146,16 @@ func (ac *attachedClient) clearCaptureFrames() { | |||
| 147 | 146 | ac.sendMu.Unlock() | |
| 148 | 147 | } | |
| 149 | 148 | ||
| 150 | - | func (ac *attachedClient) getTheme() themeui.Theme { | |
| 151 | - | return ac.getAppliedTheme().Raw | |
| 152 | - | } | |
| 153 | - | ||
| 154 | 149 | func (ac *attachedClient) getAppliedTheme() appliedTheme { | |
| 155 | 150 | ac.themeMu.Lock() | |
| 156 | 151 | defer ac.themeMu.Unlock() | |
| 157 | - | if ac.appliedTheme.Generation == 0 { | |
| 152 | + | applied := ac.appliedTheme | |
| 153 | + | if applied.Generation == 0 { | |
| 158 | 154 | // An unattached client has no terminal report yet. Reuse the static | |
| 159 | 155 | // neutral cache rather than resolving from a render path. | |
| 160 | - | return appliedTheme{Raw: ac.theme, Resolved: themeui.ResolvedTheme{Theme: ac.theme, Styles: fallbackChromeStyles}} | |
| 156 | + | applied.Resolved = themeui.ResolvedTheme{Theme: applied.Raw, Styles: fallbackChromeStyles} | |
| 161 | 157 | } | |
| 162 | - | return ac.appliedTheme | |
| 158 | + | return applied | |
| 163 | 159 | } | |
| 164 | 160 | ||
| 165 | 161 | func (ac *attachedClient) getClientTheme() themeui.Theme { | |
| @@ -168,9 +164,10 @@ func (ac *attachedClient) getClientTheme() themeui.Theme { | |||
| 168 | 164 | return ac.clientTheme | |
| 169 | 165 | } | |
| 170 | 166 | ||
| 171 | - | func (ac *attachedClient) setTheme(t themeui.Theme) { | |
| 172 | - | // Test and legacy setup paths have no daemon policy context. Keep their | |
| 173 | - | // neutral automatic behavior while publishing a complete immutable value. | |
| 167 | + | // setThemeForTest publishes a complete applied snapshot for tests that do | |
| 168 | + | // not exercise daemon configuration. Production theme changes use | |
| 169 | + | // applyHostThemeLocked, which supplies the active policy. | |
| 170 | + | func (ac *attachedClient) setThemeForTest(t themeui.Theme) { | |
| 174 | 171 | ac.setAppliedTheme(appliedTheme{Raw: t, Resolved: themeui.Resolve(t, domain.ThemeAccent{Mode: domain.ThemeAccentAuto})}) | |
| 175 | 172 | } | |
| 176 | 173 | ||
| @@ -178,7 +175,6 @@ func (ac *attachedClient) setAppliedTheme(next appliedTheme) { | |||
| 178 | 175 | ac.themeMu.Lock() | |
| 179 | 176 | next.Generation = ac.appliedTheme.Generation + 1 | |
| 180 | 177 | ac.appliedTheme = next | |
| 181 | - | ac.theme = next.Raw | |
| 182 | 178 | ac.themeMu.Unlock() | |
| 183 | 179 | } | |
| 184 | 180 | ||
| @@ -57,7 +57,6 @@ type attachedClient struct { | |||||
| 57 | sess Guarded[*session] | 57 | sess Guarded[*session] | ||
| 58 | mouseScan mouse.Scanner | 58 | mouseScan mouse.Scanner | ||
| 59 | themeMu sync.Mutex | 59 | themeMu sync.Mutex | ||
| 60 | - | theme themeui.Theme // compatibility mirror of applied.Raw | |||
| 61 | clientTheme themeui.Theme | 60 | clientTheme themeui.Theme | ||
| 62 | appliedTheme appliedTheme | 61 | appliedTheme appliedTheme | ||
| 63 | lastCursor cursorOut | 62 | lastCursor cursorOut | ||
| @@ -147,19 +146,16 @@ func (ac *attachedClient) clearCaptureFrames() { | |||||
| 147 | ac.sendMu.Unlock() | 146 | ac.sendMu.Unlock() | ||
| 148 | } | 147 | } | ||
| 149 | 148 | ||||
| 150 | - | func (ac *attachedClient) getTheme() themeui.Theme { | |||
| 151 | - | return ac.getAppliedTheme().Raw | |||
| 152 | - | } | |||
| 153 | - | ||||
| 154 | func (ac *attachedClient) getAppliedTheme() appliedTheme { | 149 | func (ac *attachedClient) getAppliedTheme() appliedTheme { | ||
| 155 | ac.themeMu.Lock() | 150 | ac.themeMu.Lock() | ||
| 156 | defer ac.themeMu.Unlock() | 151 | defer ac.themeMu.Unlock() | ||
| 157 | - | if ac.appliedTheme.Generation == 0 { | 152 | + | applied := ac.appliedTheme |
| 153 | + | if applied.Generation == 0 { | |||
| 158 | // An unattached client has no terminal report yet. Reuse the static | 154 | // An unattached client has no terminal report yet. Reuse the static | ||
| 159 | // neutral cache rather than resolving from a render path. | 155 | // neutral cache rather than resolving from a render path. | ||
| 160 | - | return appliedTheme{Raw: ac.theme, Resolved: themeui.ResolvedTheme{Theme: ac.theme, Styles: fallbackChromeStyles}} | 156 | + | applied.Resolved = themeui.ResolvedTheme{Theme: applied.Raw, Styles: fallbackChromeStyles} |
| 161 | } | 157 | } | ||
| 162 | - | return ac.appliedTheme | 158 | + | return applied |
| 163 | } | 159 | } | ||
| 164 | 160 | ||||
| 165 | func (ac *attachedClient) getClientTheme() themeui.Theme { | 161 | func (ac *attachedClient) getClientTheme() themeui.Theme { | ||
| @@ -168,9 +164,10 @@ func (ac *attachedClient) getClientTheme() themeui.Theme { | |||||
| 168 | return ac.clientTheme | 164 | return ac.clientTheme | ||
| 169 | } | 165 | } | ||
| 170 | 166 | ||||
| 171 | - | func (ac *attachedClient) setTheme(t themeui.Theme) { | 167 | + | // setThemeForTest publishes a complete applied snapshot for tests that do |
| 172 | - | // Test and legacy setup paths have no daemon policy context. Keep their | 168 | + | // not exercise daemon configuration. Production theme changes use |
| 173 | - | // neutral automatic behavior while publishing a complete immutable value. | 169 | + | // applyHostThemeLocked, which supplies the active policy. |
| 170 | + | func (ac *attachedClient) setThemeForTest(t themeui.Theme) { | |||
| 174 | ac.setAppliedTheme(appliedTheme{Raw: t, Resolved: themeui.Resolve(t, domain.ThemeAccent{Mode: domain.ThemeAccentAuto})}) | 171 | ac.setAppliedTheme(appliedTheme{Raw: t, Resolved: themeui.Resolve(t, domain.ThemeAccent{Mode: domain.ThemeAccentAuto})}) | ||
| 175 | } | 172 | } | ||
| 176 | 173 | ||||
| @@ -178,7 +175,6 @@ func (ac *attachedClient) setAppliedTheme(next appliedTheme) { | |||||
| 178 | ac.themeMu.Lock() | 175 | ac.themeMu.Lock() | ||
| 179 | next.Generation = ac.appliedTheme.Generation + 1 | 176 | next.Generation = ac.appliedTheme.Generation + 1 | ||
| 180 | ac.appliedTheme = next | 177 | ac.appliedTheme = next | ||
| 181 | - | ac.theme = next.Raw | |||
| 182 | ac.themeMu.Unlock() | 178 | ac.themeMu.Unlock() | ||
| 183 | } | 179 | } | ||
| 184 | 180 | ||||
M internal/usecase/daemon/config_test.go +2 -2
| @@ -72,7 +72,7 @@ func TestApplyThemeCopiesTerminalPaletteAndHotReloadGatesIt(t *testing.T) { | |||
| 72 | 72 | ||
| 73 | 73 | require.Equal(t, clientPalette, ac.getClientTheme().Palette) | |
| 74 | 74 | require.Equal(t, uint16(1<<1|1<<12), ac.getClientTheme().PaletteKnown) | |
| 75 | - | require.True(t, ac.getTheme().UsePalette) | |
| 75 | + | require.True(t, ac.getAppliedTheme().Raw.UsePalette) | |
| 76 | 76 | ||
| 77 | 77 | cfg := domain.Defaults() | |
| 78 | 78 | cfg.ThemePalette = false | |
| @@ -80,7 +80,7 @@ func TestApplyThemeCopiesTerminalPaletteAndHotReloadGatesIt(t *testing.T) { | |||
| 80 | 80 | ||
| 81 | 81 | // ApplyConfig reuses the live client report and repaints without requiring | |
| 82 | 82 | // a reconnect; only palette inheritance changes. | |
| 83 | - | reloaded := ac.getTheme() | |
| 83 | + | reloaded := ac.getAppliedTheme().Raw | |
| 84 | 84 | require.Equal(t, clientPalette, reloaded.Palette) | |
| 85 | 85 | require.Equal(t, uint16(1<<1|1<<12), reloaded.PaletteKnown) | |
| 86 | 86 | require.False(t, reloaded.UsePalette) | |
| @@ -72,7 +72,7 @@ func TestApplyThemeCopiesTerminalPaletteAndHotReloadGatesIt(t *testing.T) { | |||||
| 72 | 72 | ||||
| 73 | require.Equal(t, clientPalette, ac.getClientTheme().Palette) | 73 | require.Equal(t, clientPalette, ac.getClientTheme().Palette) | ||
| 74 | require.Equal(t, uint16(1<<1|1<<12), ac.getClientTheme().PaletteKnown) | 74 | require.Equal(t, uint16(1<<1|1<<12), ac.getClientTheme().PaletteKnown) | ||
| 75 | - | require.True(t, ac.getTheme().UsePalette) | 75 | + | require.True(t, ac.getAppliedTheme().Raw.UsePalette) |
| 76 | 76 | ||||
| 77 | cfg := domain.Defaults() | 77 | cfg := domain.Defaults() | ||
| 78 | cfg.ThemePalette = false | 78 | cfg.ThemePalette = false | ||
| @@ -80,7 +80,7 @@ func TestApplyThemeCopiesTerminalPaletteAndHotReloadGatesIt(t *testing.T) { | |||||
| 80 | 80 | ||||
| 81 | // ApplyConfig reuses the live client report and repaints without requiring | 81 | // ApplyConfig reuses the live client report and repaints without requiring | ||
| 82 | // a reconnect; only palette inheritance changes. | 82 | // a reconnect; only palette inheritance changes. | ||
| 83 | - | reloaded := ac.getTheme() | 83 | + | reloaded := ac.getAppliedTheme().Raw |
| 84 | require.Equal(t, clientPalette, reloaded.Palette) | 84 | require.Equal(t, clientPalette, reloaded.Palette) | ||
| 85 | require.Equal(t, uint16(1<<1|1<<12), reloaded.PaletteKnown) | 85 | require.Equal(t, uint16(1<<1|1<<12), reloaded.PaletteKnown) | ||
| 86 | require.False(t, reloaded.UsePalette) | 86 | require.False(t, reloaded.UsePalette) | ||
M internal/usecase/daemon/render_test.go +3 -3
| @@ -30,7 +30,7 @@ func TestPaletteBackdropDimsSimultaneousCopyMode(t *testing.T) { | |||
| 30 | 30 | defer release() | |
| 31 | 31 | d, sess, ac, sends := newManualSessionWithPTYs(t, p) | |
| 32 | 32 | theme := backdropTheme() | |
| 33 | - | ac.setTheme(theme) | |
| 33 | + | ac.setThemeForTest(theme) | |
| 34 | 34 | client := vt.NewScreen(80, 25) | |
| 35 | 35 | pane := sess.tabs[0].focusedPane() | |
| 36 | 36 | pane.screen.Write([]byte("\x1b[38;2;180;90;30mX")) | |
| @@ -96,7 +96,7 @@ func TestPaletteBackdropKeepsSimultaneousPickerCrisp(t *testing.T) { | |||
| 96 | 96 | p, release := newBlockingPTY(t) | |
| 97 | 97 | defer release() | |
| 98 | 98 | d, sess, ac, sends := newManualSessionWithPTYs(t, p) | |
| 99 | - | ac.setTheme(backdropTheme()) | |
| 99 | + | ac.setThemeForTest(backdropTheme()) | |
| 100 | 100 | client := vt.NewScreen(80, 25) | |
| 101 | 101 | pane := sess.tabs[0].focusedPane() | |
| 102 | 102 | pane.screen.Write([]byte("X")) | |
| @@ -117,7 +117,7 @@ func TestPaletteBackdropProductionRenderAndDismissal(t *testing.T) { | |||
| 117 | 117 | p, release := newBlockingPTY(t) | |
| 118 | 118 | defer release() | |
| 119 | 119 | d, sess, ac, sends := newManualSessionWithPTYs(t, p) | |
| 120 | - | ac.setTheme(backdropTheme()) | |
| 120 | + | ac.setThemeForTest(backdropTheme()) | |
| 121 | 121 | client := vt.NewScreen(80, 25) | |
| 122 | 122 | pane := sess.tabs[0].focusedPane() | |
| 123 | 123 | pane.screen.Write([]byte("X")) | |
| @@ -30,7 +30,7 @@ func TestPaletteBackdropDimsSimultaneousCopyMode(t *testing.T) { | |||||
| 30 | defer release() | 30 | defer release() | ||
| 31 | d, sess, ac, sends := newManualSessionWithPTYs(t, p) | 31 | d, sess, ac, sends := newManualSessionWithPTYs(t, p) | ||
| 32 | theme := backdropTheme() | 32 | theme := backdropTheme() | ||
| 33 | - | ac.setTheme(theme) | 33 | + | ac.setThemeForTest(theme) |
| 34 | client := vt.NewScreen(80, 25) | 34 | client := vt.NewScreen(80, 25) | ||
| 35 | pane := sess.tabs[0].focusedPane() | 35 | pane := sess.tabs[0].focusedPane() | ||
| 36 | pane.screen.Write([]byte("\x1b[38;2;180;90;30mX")) | 36 | pane.screen.Write([]byte("\x1b[38;2;180;90;30mX")) | ||
| @@ -96,7 +96,7 @@ func TestPaletteBackdropKeepsSimultaneousPickerCrisp(t *testing.T) { | |||||
| 96 | p, release := newBlockingPTY(t) | 96 | p, release := newBlockingPTY(t) | ||
| 97 | defer release() | 97 | defer release() | ||
| 98 | d, sess, ac, sends := newManualSessionWithPTYs(t, p) | 98 | d, sess, ac, sends := newManualSessionWithPTYs(t, p) | ||
| 99 | - | ac.setTheme(backdropTheme()) | 99 | + | ac.setThemeForTest(backdropTheme()) |
| 100 | client := vt.NewScreen(80, 25) | 100 | client := vt.NewScreen(80, 25) | ||
| 101 | pane := sess.tabs[0].focusedPane() | 101 | pane := sess.tabs[0].focusedPane() | ||
| 102 | pane.screen.Write([]byte("X")) | 102 | pane.screen.Write([]byte("X")) | ||
| @@ -117,7 +117,7 @@ func TestPaletteBackdropProductionRenderAndDismissal(t *testing.T) { | |||||
| 117 | p, release := newBlockingPTY(t) | 117 | p, release := newBlockingPTY(t) | ||
| 118 | defer release() | 118 | defer release() | ||
| 119 | d, sess, ac, sends := newManualSessionWithPTYs(t, p) | 119 | d, sess, ac, sends := newManualSessionWithPTYs(t, p) | ||
| 120 | - | ac.setTheme(backdropTheme()) | 120 | + | ac.setThemeForTest(backdropTheme()) |
| 121 | client := vt.NewScreen(80, 25) | 121 | client := vt.NewScreen(80, 25) | ||
| 122 | pane := sess.tabs[0].focusedPane() | 122 | pane := sess.tabs[0].focusedPane() | ||
| 123 | pane.screen.Write([]byte("X")) | 123 | pane.screen.Write([]byte("X")) | ||
M internal/usecase/daemon/status_test.go +6 -6
| @@ -408,7 +408,7 @@ func TestStatusCompositionUsesTruecolorTheme(t *testing.T) { | |||
| 408 | 408 | win := sess.activeTab() | |
| 409 | 409 | win.focusedPane().screen.Resize(12, 2) | |
| 410 | 410 | win.size = domain.Size{Cols: 12, Rows: 2} | |
| 411 | - | ac.setTheme(themeui.Theme{ | |
| 411 | + | ac.setThemeForTest(themeui.Theme{ | |
| 412 | 412 | Foreground: renderer.RGB{R: 220, G: 220, B: 220}, | |
| 413 | 413 | Background: renderer.RGB{R: 10, G: 20, B: 30}, | |
| 414 | 414 | HasFG: true, | |
| @@ -438,9 +438,9 @@ func TestStatusApplyThemeStoresClientAndPropagatesScreens(t *testing.T) { | |||
| 438 | 438 | ||
| 439 | 439 | d.applyTheme(sess, ac, msg) | |
| 440 | 440 | ||
| 441 | - | require.Equal(t, renderer.RGB{R: 1, G: 2, B: 3}, ac.getTheme().Foreground) | |
| 442 | - | require.True(t, ac.getTheme().SchemeKnown) | |
| 443 | - | require.True(t, ac.getTheme().Light) | |
| 441 | + | require.Equal(t, renderer.RGB{R: 1, G: 2, B: 3}, ac.getAppliedTheme().Raw.Foreground) | |
| 442 | + | require.True(t, ac.getAppliedTheme().Raw.SchemeKnown) | |
| 443 | + | require.True(t, ac.getAppliedTheme().Raw.Light) | |
| 444 | 444 | assertSessionDefaultColors(t, sess, renderer.RGB{R: 1, G: 2, B: 3}, renderer.RGB{R: 4, G: 5, B: 6}) | |
| 445 | 445 | assertSessionColorScheme(t, sess, true) | |
| 446 | 446 | } | |
| @@ -494,7 +494,7 @@ func TestApplyThemeForcedBuiltinThemePropagatesToChromeAndPanes(t *testing.T) { | |||
| 494 | 494 | TrueColor: true, SchemeKnown: true, Light: !tc.want.Light, | |
| 495 | 495 | }) | |
| 496 | 496 | ||
| 497 | - | require.Equal(t, tc.want, ac.getTheme()) | |
| 497 | + | require.Equal(t, tc.want, ac.getAppliedTheme().Raw) | |
| 498 | 498 | assertSessionDefaultColors(t, sess, tc.want.Foreground, tc.want.Background) | |
| 499 | 499 | assertSessionColorScheme(t, sess, tc.want.Light) | |
| 500 | 500 | }) | |
| @@ -530,7 +530,7 @@ func TestAttachClientAppliesForcedThemeBeforeMsgTheme(t *testing.T) { | |||
| 530 | 530 | ||
| 531 | 531 | ac, _ := d.attachClient(sess, tr, domain.Size{Cols: 80, Rows: 24}, attachClientOptions{}) | |
| 532 | 532 | ||
| 533 | - | require.Equal(t, tc.want, ac.getTheme()) | |
| 533 | + | require.Equal(t, tc.want, ac.getAppliedTheme().Raw) | |
| 534 | 534 | assertSessionDefaultColors(t, sess, tc.want.Foreground, tc.want.Background) | |
| 535 | 535 | assertSessionColorScheme(t, sess, tc.want.Light) | |
| 536 | 536 | }) | |
| @@ -408,7 +408,7 @@ func TestStatusCompositionUsesTruecolorTheme(t *testing.T) { | |||||
| 408 | win := sess.activeTab() | 408 | win := sess.activeTab() | ||
| 409 | win.focusedPane().screen.Resize(12, 2) | 409 | win.focusedPane().screen.Resize(12, 2) | ||
| 410 | win.size = domain.Size{Cols: 12, Rows: 2} | 410 | win.size = domain.Size{Cols: 12, Rows: 2} | ||
| 411 | - | ac.setTheme(themeui.Theme{ | 411 | + | ac.setThemeForTest(themeui.Theme{ |
| 412 | Foreground: renderer.RGB{R: 220, G: 220, B: 220}, | 412 | Foreground: renderer.RGB{R: 220, G: 220, B: 220}, | ||
| 413 | Background: renderer.RGB{R: 10, G: 20, B: 30}, | 413 | Background: renderer.RGB{R: 10, G: 20, B: 30}, | ||
| 414 | HasFG: true, | 414 | HasFG: true, | ||
| @@ -438,9 +438,9 @@ func TestStatusApplyThemeStoresClientAndPropagatesScreens(t *testing.T) { | |||||
| 438 | 438 | ||||
| 439 | d.applyTheme(sess, ac, msg) | 439 | d.applyTheme(sess, ac, msg) | ||
| 440 | 440 | ||||
| 441 | - | require.Equal(t, renderer.RGB{R: 1, G: 2, B: 3}, ac.getTheme().Foreground) | 441 | + | require.Equal(t, renderer.RGB{R: 1, G: 2, B: 3}, ac.getAppliedTheme().Raw.Foreground) |
| 442 | - | require.True(t, ac.getTheme().SchemeKnown) | 442 | + | require.True(t, ac.getAppliedTheme().Raw.SchemeKnown) |
| 443 | - | require.True(t, ac.getTheme().Light) | 443 | + | require.True(t, ac.getAppliedTheme().Raw.Light) |
| 444 | assertSessionDefaultColors(t, sess, renderer.RGB{R: 1, G: 2, B: 3}, renderer.RGB{R: 4, G: 5, B: 6}) | 444 | assertSessionDefaultColors(t, sess, renderer.RGB{R: 1, G: 2, B: 3}, renderer.RGB{R: 4, G: 5, B: 6}) | ||
| 445 | assertSessionColorScheme(t, sess, true) | 445 | assertSessionColorScheme(t, sess, true) | ||
| 446 | } | 446 | } | ||
| @@ -494,7 +494,7 @@ func TestApplyThemeForcedBuiltinThemePropagatesToChromeAndPanes(t *testing.T) { | |||||
| 494 | TrueColor: true, SchemeKnown: true, Light: !tc.want.Light, | 494 | TrueColor: true, SchemeKnown: true, Light: !tc.want.Light, | ||
| 495 | }) | 495 | }) | ||
| 496 | 496 | ||||
| 497 | - | require.Equal(t, tc.want, ac.getTheme()) | 497 | + | require.Equal(t, tc.want, ac.getAppliedTheme().Raw) |
| 498 | assertSessionDefaultColors(t, sess, tc.want.Foreground, tc.want.Background) | 498 | assertSessionDefaultColors(t, sess, tc.want.Foreground, tc.want.Background) | ||
| 499 | assertSessionColorScheme(t, sess, tc.want.Light) | 499 | assertSessionColorScheme(t, sess, tc.want.Light) | ||
| 500 | }) | 500 | }) | ||
| @@ -530,7 +530,7 @@ func TestAttachClientAppliesForcedThemeBeforeMsgTheme(t *testing.T) { | |||||
| 530 | 530 | ||||
| 531 | ac, _ := d.attachClient(sess, tr, domain.Size{Cols: 80, Rows: 24}, attachClientOptions{}) | 531 | ac, _ := d.attachClient(sess, tr, domain.Size{Cols: 80, Rows: 24}, attachClientOptions{}) | ||
| 532 | 532 | ||||
| 533 | - | require.Equal(t, tc.want, ac.getTheme()) | 533 | + | require.Equal(t, tc.want, ac.getAppliedTheme().Raw) |
| 534 | assertSessionDefaultColors(t, sess, tc.want.Foreground, tc.want.Background) | 534 | assertSessionDefaultColors(t, sess, tc.want.Foreground, tc.want.Background) | ||
| 535 | assertSessionColorScheme(t, sess, tc.want.Light) | 535 | assertSessionColorScheme(t, sess, tc.want.Light) | ||
| 536 | }) | 536 | }) | ||