Emacs Transient Expert
This skill provides comprehensive guidance for building transient-based interfaces in Emacs Lisp, based on the official transient library and real-world implementations in Magit, Forge, and transient-showcase.
When to Use This Skill
Invoke this skill when:
- •Implementing new
transient-define-prefixmenus - •Creating
transient-define-suffixortransient-define-infixcommands - •Debugging transient menu layouts or behavior
- •Understanding transient levels, groups, or conditional display
- •Working on keyboard-driven UI similar to Magit
- •Converting traditional Emacs commands to transient interfaces
Core Concepts
What is Transient?
Transient is a library for creating keyboard-driven, temporary menus in Emacs. It's the foundation of Magit's interface and provides:
- •Display of current state - Show active arguments and options
- •Modal bindings - Temporary keymaps that disappear when done
- •Contextual UI - Menus adapt based on state
- •Persistence - Save and restore argument values across sessions
- •History - Track previously used values
Key Terminology
- •Prefix - The main transient command that opens the menu
- •Suffix - Commands invoked from the transient (actions)
- •Infix - Special suffixes that set arguments/options without exiting
- •Groups - Organizational units for layout (rows, columns, sections)
- •Levels (1-7) - Control visibility based on user expertise (default: 4)
- •Scope - Contextual value passed to suffixes (e.g., current branch)
Three Core Macros
1. transient-define-prefix
Defines the main transient menu.
(transient-define-prefix my-menu ()
"Description of what this menu does."
:man-page "git-commit" ; Optional: link to man page
:info-manual "(magit)Committing" ; Optional: link to info manual
:value '("--verbose") ; Optional: default arguments
;; Groups define layout
["Arguments" ; Group header
("-v" "Verbose" "--verbose") ; Switch (toggle)
("-a" "Author" "--author=" ; Option (takes value)
:prompt "Author: ")
(my-custom-infix)] ; Reference to defined infix
[["Actions" ; Nested groups = columns
("c" "Commit" my-commit-cmd)
("a" "Amend" my-amend-cmd)]
["Other"
("q" "Quit" transient-quit)]])
Key slots:
- •
:value- Default arguments - •
:man-page- Man page for help - •
:info-manual- Info manual section - •
:transient-suffix- Default transient behavior for suffixes - •
:transient-non-suffix- Allow/forbid non-suffix commands - •
:refresh-suffixes- When to refresh suffix state
2. transient-define-suffix
Defines action commands (suffixes).
(transient-define-suffix my-commit-cmd (args) "Create a commit with ARGS." :description "Commit staged changes" ; Optional: override in menu :transient t ; Stay transient after calling (interactive (list (transient-args 'my-menu))) (apply #'my-run-git "commit" args))
Key slots:
- •
:key- Key binding (can override menu binding) - •
:description- Can be string or function returning string - •
:transient- Control transient state (see below) - •
:if/:if-not- Conditional visibility - •
:inapt-if/:inapt-if-not- Show but disable
3. transient-define-infix (or transient-define-argument)
Defines argument commands (infixes).
(transient-define-argument my-author-arg () :description "Set author" :class 'transient-option ; Option class (takes value) :shortarg "-a" ; Short form :argument "--author=" ; Long form :reader #'my-read-author) ; Custom reader function (transient-define-infix my-verbose-switch () :description "Verbose output" :class 'transient-switch ; Switch class (boolean) :argument "--verbose")
Infix/Suffix Classes
Suffix Classes
- •
transient-suffix- Base class for all suffixes - •
transient-infix- Base for all infixes (auto-stays transient)
Infix Classes (derive from transient-infix)
For command-line arguments:
- •
transient-switch- Boolean flag (e.g.,--verbose) - •
transient-option- Argument with value (e.g.,--author=NAME) - •
transient-switches- Mutually exclusive options - •
transient-files- File arguments (--separator)
For variables:
- •
transient-variable- Base for variable infixes - •
transient-lisp-variable- Set Emacs Lisp variables
Display only:
- •
transient-information- Display info (no command/key) - •
transient-information*- Info aligned with descriptions
Suffix Specification Syntax
Three ways to specify suffixes:
1. Inline (shorthand)
("key" "description" command)
("-s" "switch" "--switch") ; Auto-creates transient-switch
("-o" "option" "--option=") ; Auto-creates transient-option
2. With keyword arguments
("key" "description" command
:transient t ; Stay transient
:if (lambda () (my-condition))) ; Conditional
("-a" "author" "--author="
:prompt "Author name: "
:reader #'my-custom-reader
:always-read t) ; Always prompt, don't toggle
3. Reference to separately defined command
(my-custom-suffix) ; Uses suffix's own key/desc
Group Specification Syntax
[{LEVEL} {DESCRIPTION} {KEYWORD VALUE}... ELEMENT...]
Common patterns:
["Group Title" ; Simple group with title
("k" "desc" cmd)]
[:description "Dynamic" ; Dynamic description
:description (lambda () (format "Time: %s" (current-time-string)))
("k" "desc" cmd)]
[:if some-predicate ; Conditional group
("k" "desc" cmd)]
[:class transient-row ; Explicit layout class
("k" "desc" cmd)]
[["Column 1" ; Nested groups = columns
("a" "cmd a" cmd-a)]
["Column 2"
("b" "cmd b" cmd-b)]]
Group classes:
- •
transient-column- Stack items vertically (default) - •
transient-row- Arrange items horizontally - •
transient-columns- Contains column groups side-by-side - •
transient-subgroups- Contains subgroups
The :transient Slot (Controlling State)
Controls whether transient stays active after invoking a command.
For suffixes (default: exit transient):
- •
nilor:transient nil- Exit transient (default for suffixes) - •
tor:transient t- Stay transient - •
:transient 'transient--do-call- Export args and stay - •
:transient 'transient--do-return- Return to parent prefix
For infixes (default: stay transient):
- •Infixes always use
transient--do-stayby default - •Rarely need to override
For sub-prefixes (nested transients):
- •
nil- Exit all transients when sub-prefix exits - •
t- Return to parent when sub-prefix exits - •
:transient 'transient--do-recurse- Enable return behavior - •
:transient 'transient--do-replace- Replace parent (no return)
Common pre-commands:
- •
transient--do-exit- Exit and export args - •
transient--do-stay- Stay, don't export args - •
transient--do-call- Stay and export args - •
transient--do-return- Exit to parent prefix
Layout Patterns
Stacked Groups (Vertical)
(transient-define-prefix my-menu () ["Top Group" ...] ["Bottom Group" ...])
Columns (Side-by-side)
(transient-define-prefix my-menu () [["Left Column" ...] ["Right Column" ...]])
Mixed (Stacked + Columns)
(transient-define-prefix my-menu () ["Top Group (full width)" ...] [["Left Column" ...] ["Right Column" ...]])
Grid Layout
(transient-define-prefix my-menu ()
[:description "The Grid"
["Left Column"
("tl" "top-left" cmd)
("bl" "bottom-left" cmd)]
["Right Column"
("tr" "top-right" cmd)
("br" "bottom-right" cmd)]])
Spacing
["Group"
"" ; Empty line
("k" "first" cmd)
("l" "second" cmd)
"" ; Another gap
("m" "third" cmd)]
Accessing Transient Values
In suffix commands
(transient-define-suffix my-suffix (args) "Do something with ARGS." (interactive (list (transient-args 'my-prefix))) ;; Now use args... (message "Args: %S" args))
Getting specific argument values
(let* ((args (transient-args 'my-prefix))
(author (transient-arg-value "--author=" args))
(verbose-p (transient-arg-value "--verbose" args)))
;; Use values...
)
Using scope (contextual value)
(transient-define-prefix my-menu (scope)
"Menu with scope."
["Actions"
("a" "Action" my-action)]
(interactive "P") ; Can take prefix arg as scope
(transient-setup 'my-menu nil nil :scope scope))
(transient-define-suffix my-action ()
(interactive)
(let ((scope (transient-scope)))
(message "Scope: %S" scope)))
Conditional Display (Predicates)
Visibility predicates (if suffix should appear)
("k" "desc" cmd
:if (lambda () (file-exists-p "Makefile"))) ; Show if true
("k" "desc" cmd
:if-not some-mode ; Show if not in mode
:if-non-nil some-variable ; Show if var non-nil
:if-mode 'emacs-lisp-mode ; Show in mode
:if-derived 'prog-mode) ; Show if derived
Inapt predicates (show but grayed out)
("k" "desc" cmd
:inapt-if (lambda () (not (magit-anything-staged-p)))) ; Gray if true
("k" "desc" cmd
:inapt-if-not some-function ; Gray if false
:inapt-if-nil some-variable) ; Gray if var nil
On groups
[:if magit-rebase-in-progress-p ; Whole group conditional
("a" "abort" magit-rebase-abort)
("c" "continue" magit-rebase-continue)]
Important Suffix Slots
Required/Common:
- •
:key- Key binding - •
:description- String or function returning string - •
:command- The command to invoke
Behavioral:
- •
:transient- Stay transient? (t/nil/pre-command) - •
:level- Visibility level (1-7, default 4)
Conditional:
- •
:if,:if-not,:if-mode,:if-derived- Visibility - •
:inapt-if,:inapt-if-not- Enable/disable
Display:
- •
:format- Custom display format (%k%d%v) - •
:face- Face for description - •
:summary- Echo area/tooltip text
Help:
- •
:show-help- Custom help function
Important Infix Slots
All suffix slots, plus:
Argument-related:
- •
:argument- Long form (e.g.,--verbose) - •
:shortarg- Short form (e.g.,-v) - •
:class- Infix class (switch/option/etc.)
Reading values:
- •
:reader- Function to read value (PROMPT, INITIAL, HISTORY) - •
:prompt- Prompt string or function - •
:choices- List of valid values - •
:always-read- Always prompt (don't toggle for options) - •
:allow-empty- Allow empty string
Multi-value:
- •
:multi-value-'restor'repeatfor multiple values
Other:
- •
:init-value- Function to set initial value - •
:history-key- Symbol for history (share across infixes) - •
:unsavable- Don't save with prefix value
Levels (1-7)
Control visibility based on user preference:
- •1-3: Essential commands (always visible)
- •4: Default level
- •5-6: Advanced/rarely used
- •7: Experimental/debug
- •0: Never show (effectively disabled)
("k" "advanced" cmd :level 6) ; Only show at level 6+
["Arguments" :level 5 ; Whole group at level 5
("-v" "verbose" "--verbose")]
Users can change levels interactively with C-x l.
Dynamic Content
Dynamic descriptions
("k" my-cmd
:description (lambda ()
(format "Branch: %s" (magit-get-current-branch))))
Dynamic groups (:setup-children)
[:class transient-column
:setup-children
(lambda (_)
(transient-parse-suffixes
'my-prefix
(mapcar (lambda (file)
(list (substring file 0 1) file
(lambda () (interactive) (find-file file))))
(directory-files "."))))]
Information display
["Info" (:info "Static information") (:info (lambda () (format "Dynamic: %s" (current-time-string)))) (:info my-info-function :format " %d")] ; Custom format
Common Patterns from Magit
Context-aware suffixes
(transient-define-suffix my-cmd (args)
(interactive
(if (derived-mode-p 'my-list-mode)
(list (my-get-args-from-buffer))
(list (transient-args 'my-prefix))))
...)
Shared argument groups
(transient-define-group my-common-args ()
["Common Arguments"
("-v" "Verbose" "--verbose")
("-q" "Quiet" "--quiet")])
(transient-define-prefix my-menu-1 ()
[my-common-args] ; Include by reference
["Actions" ...])
(transient-define-prefix my-menu-2 ()
[my-common-args] ; Reuse in another menu
["Other Actions" ...])
Validation before execution
(transient-define-suffix my-create (title desc)
(interactive
(list (read-string "Title: ")
(read-string "Description: ")))
(when (string-empty-p title)
(user-error "Title cannot be empty"))
(my-create-thing title desc))
Best Practices
1. Use appropriate classes
- •Use
transient-switchfor boolean flags - •Use
transient-optionfor value-taking arguments - •Use
transient-switchesfor mutually exclusive options
2. Leverage levels effectively
- •Put common operations at level 3-4
- •Put advanced features at level 5-6
- •Use level 7 for debug/experimental
3. Provide good descriptions
- •Keep descriptions concise (fits in menu)
- •Use dynamic descriptions to show state
- •Use
:summaryfor longer explanations
4. Handle state properly
- •Use
:transient tfor commands that should stay in menu - •Use
:ifpredicates instead of manual state checking - •Use
:inapt-ifto show unavailable options
5. Organize logically
- •Group related items together
- •Use columns for parallel choices
- •Put common actions on left, advanced on right
6. Share history
- •Use
:history-keyto share history between similar infixes
(transient-define-argument my-author () :argument "--author=" :history-key 'my-package-author-history)
7. Provide help
- •Set
:man-pageor:info-manualon prefix - •Use descriptive docstrings
- •Implement custom
:show-helpif needed
8. Test interactively
- •Use
C-hwhile transient is active to see help - •Use
C-x lto experiment with levels - •Use
C-x s/C-x C-sto test persistence
Common Gotchas
- •
Don't quote in transient definitions - The macro handles it
elisp;; WRONG: ["Group" '("k" "desc" 'cmd)] ;; RIGHT: ["Group" ("k" "desc" cmd)] - •
Use :transient t for iterative commands
elisp("n" "next" my-next :transient t) ; Can press 'n' repeatedly - •
Remember infixes stay transient by default
- •Don't need
:transient ton infixes - •They automatically use
transient--do-stay
- •Don't need
- •
:if vs :inapt-if
- •
:if- completely hide the suffix - •
:inapt-if- show but grayed out
- •
- •
Accessing args in interactive
elisp(interactive (list (transient-args 'my-prefix))) ; Correct
- •
Sub-prefix returns
elisp("s" "sub-menu" my-sub-prefix :transient t) ; Returns to parent
Complete Example
;; Custom argument
(transient-define-argument my-pkg:--author ()
:description "Override author"
:class 'transient-option
:shortarg "-a"
:argument "--author="
:reader #'my-read-author)
;; Suffix that stays transient
(transient-define-suffix my-pkg-preview ()
"Preview current settings."
:transient t
(interactive)
(message "Args: %S" (transient-args 'my-pkg-create)))
;; Main suffix
(transient-define-suffix my-pkg-execute (args)
"Execute with ARGS."
(interactive (list (transient-args 'my-pkg-create)))
(apply #'my-pkg-run args))
;; Main menu
(transient-define-prefix my-pkg-create ()
"Create something with options."
:man-page "my-tool"
:value '("--verbose")
["Arguments"
("-v" "Verbose" "--verbose")
("-q" "Quiet" "--quiet")
("-n" "Dry run" "--dry-run" :level 5)
(my-pkg:--author)]
[["Actions"
("p" "Preview" my-pkg-preview)
("c" "Create" my-pkg-execute)]
["Other"
("q" "Quit" transient-quit)]])
Reference Materials
For deeper understanding, refer to:
- •transient-reference/transient/ - Core library documentation
- •transient-reference/magit/ - Real-world usage in Magit
- •transient-reference/forge/ - Additional patterns in Forge
- •transient-reference/transient-showcase/ - Examples showcase
Testing Transient Menus
Why Test Transients?
Transient menus have complex state management involving:
- •Infix commands that set state variables
- •Suffix commands that read those states
- •Pre/post-command hooks for state transitions
- •Display refreshes and keybinding resolution
Testing only suffix commands (by mocking transient-args) skips
testing the entire UI layer.
Two Testing Approaches
1. Unit Testing (Mocked Args) - Fast but Limited
Test suffix commands directly by mocking transient-args:
(defmacro my-test-with-transient-args (prefix args &rest body)
"Execute BODY with transient-args mocked for PREFIX to return ARGS."
(declare (indent 2))
`(cl-letf (((symbol-function 'transient-args)
(lambda (p)
(when (eq p ,prefix)
,args))))
,@body))
(ert-deftest my-test-suffix-execution ()
"Test suffix command with mocked args."
(my-test-with-transient-args 'my-prefix
'("--title=Test" "--priority=1")
(let ((result (call-interactively #'my-suffix-execute)))
(should (string-match-p "Created" result)))))
Pros:
- •Fast (~0.5s per test)
- •Simple to write
- •Good for testing suffix logic
Cons:
- •❌ Doesn't test infix commands
- •❌ Doesn't test transient UI
- •❌ Doesn't test user workflow
- •❌ Won't catch binding errors
2. Integration Testing (execute-kbd-macro) - Slower but Complete
Test the full user interaction with keyboard macros:
(ert-deftest my-test-full-ui-interaction ()
"Test complete user workflow through transient UI."
:tags '(:integration :ui)
(my-test-with-project () ; Setup test environment
;; Invoke the transient menu
(funcall-interactively #'my-prefix)
;; Set title via infix (key + input in ONE macro!)
(execute-kbd-macro (kbd "t Test SPC Title RET"))
;; Set priority via infix
(execute-kbd-macro (kbd "- p 1 RET"))
;; Execute the suffix
(execute-kbd-macro (kbd "x"))
;; Verify results
(let ((result (my-get-created-item)))
(should (equal (plist-get result :title) "Test Title"))
(should (equal (plist-get result :priority) 1)))))
Pros:
- •✅ Tests infix commands
- •✅ Tests transient UI
- •✅ Tests real user workflow
- •✅ No mocking needed
- •✅ Works in batch mode (CI-friendly!)
- •✅ Catches binding errors, UI bugs
Cons:
- •Slower (~2.3s per test, +0.65s overhead)
- •More complex to write
- •Need to know key sequences
Performance breakdown:
- •Transient infrastructure: ~0.15s
- •Multiple kbd macro calls: ~0.20s
- •Minibuffer interactions: ~0.20s
- •Display refreshes: ~0.10s
- •Total overhead: ~0.65s per test
Critical Rule for execute-kbd-macro
You MUST combine transient key + input in a SINGLE kbd macro:
;; ✅ CORRECT - Key + input in one macro
(execute-kbd-macro (kbd "t Bug SPC Title RET"))
;; ❌ WRONG - Splitting key and input
(execute-kbd-macro (kbd "t")) ; Opens minibuffer
(execute-kbd-macro (kbd "Bug Title RET")) ; Fails! Tries to invoke
; transient keys B, u, g
Why? When you split the macro, the second call doesn't go to the minibuffer - it's interpreted as more transient commands.
Testing Patterns
Pattern 1: Minimal workflow (title only)
(ert-deftest my-test-minimal-workflow ()
"Test simplest possible workflow."
(my-test-setup ()
(funcall-interactively #'my-create)
(execute-kbd-macro (kbd "t Minimal SPC Test RET"))
(execute-kbd-macro (kbd "x"))
;; Verify...
))
Pattern 2: Full workflow (multiple fields)
(ert-deftest my-test-full-workflow ()
"Test complete workflow with all fields."
(my-test-setup ()
(funcall-interactively #'my-create)
;; Set all fields
(execute-kbd-macro (kbd "t Full SPC Test RET"))
(execute-kbd-macro (kbd "- t feature RET"))
(execute-kbd-macro (kbd "- p 2 RET"))
(execute-kbd-macro (kbd "- a john@example.com RET"))
;; Execute
(execute-kbd-macro (kbd "x"))
;; Verify all fields were set correctly
))
Pattern 3: Testing switches (toggle flags)
(ert-deftest my-test-switches ()
"Test boolean switch toggling."
(my-test-setup ()
(funcall-interactively #'my-prefix)
;; Toggle switch on
(execute-kbd-macro (kbd "- v")) ; --verbose
;; Toggle switch off
(execute-kbd-macro (kbd "- v"))
;; Toggle back on
(execute-kbd-macro (kbd "- v"))
(execute-kbd-macro (kbd "x"))
;; Verify switch state...
))
Pattern 4: Testing transient navigation
(ert-deftest my-test-navigation ()
"Test moving between transient levels."
(my-test-setup ()
(funcall-interactively #'my-prefix)
;; Change transient level to show advanced options
(execute-kbd-macro (kbd "C-x l 6 RET"))
;; Now advanced options should be visible
(execute-kbd-macro (kbd "- n")) ; Advanced option
(execute-kbd-macro (kbd "x"))
;; Verify...
))
Helper Functions
(defun my-test-kbd-do (keys)
"Execute keyboard macro from KEYS list.
KEYS is a list of key sequence strings that will be joined
and executed as a keyboard macro."
(execute-kbd-macro (kbd (string-join keys " "))))
;; Usage:
(my-test-kbd-do '("t" "Title" "RET")) ; Cleaner than raw kbd
When to Use Each Approach
Use mocked approach when:
- •Testing suffix logic in isolation
- •Testing error handling
- •Speed is critical (large test suite)
- •Don't need to test UI interaction
Use execute-kbd-macro approach when:
- •Testing end-to-end user workflows
- •Validating infix commands work
- •Testing transient state management
- •Need confidence in the full UI
- •Testing for a critical user path
Recommended strategy:
- •Use mocked tests for most unit tests
- •Use execute-kbd-macro for key integration tests
- •At least one full UI test per transient menu
Example: Complete Test Suite
;;; Unit Tests (Mocked - Fast)
(ert-deftest my-create-test-parse-args ()
"Test argument parsing."
;; Fast unit test, no UI
)
(ert-deftest my-create-test-validation ()
"Test validation logic."
(my-test-with-transient-args 'my-create
'("--title=") ; Empty title
(should-error (call-interactively #'my-create-execute)
:type 'user-error)))
;;; Integration Tests (Full UI - Comprehensive)
(ert-deftest my-create-test-full-ui-basic ()
"Test basic creation workflow through UI."
:tags '(:integration :ui)
(my-test-setup ()
(funcall-interactively #'my-create)
(execute-kbd-macro (kbd "t Basic SPC Test RET"))
(execute-kbd-macro (kbd "x"))
;; Verify creation...
))
(ert-deftest my-create-test-full-ui-all-fields ()
"Test creation with all fields through UI."
:tags '(:integration :ui :slow)
(my-test-setup ()
(funcall-interactively #'my-create)
(execute-kbd-macro (kbd "t Full SPC Test RET"))
(execute-kbd-macro (kbd "- t feature RET"))
(execute-kbd-macro (kbd "- p 2 RET"))
(execute-kbd-macro (kbd "x"))
;; Verify all fields...
))
Debugging Tips
When tests fail:
- •Check key bindings - Use
C-hin transient to see actual keys - •Test interactively first - Run the transient manually
- •Add debug messages:
elisp
(execute-kbd-macro (kbd "t Test RET")) (message "After title: %S" (transient-args 'my-prefix))
- •Check transient buffer - Look at
*transient*buffer state - •Use edebug - Step through suffix execution
Common issues:
- •"Unbound suffix" - Wrong key binding in test
- •"Wrong type argument" - Reader function got nil/wrong type
- •Macro fails silently - Split key + input across macros
- •Args not set - Forgot to call
funcall-interactivelyon prefix
Testing Multiline Editor Fields
Fields that open dedicated editor buffers for multiline input (description,
notes, comments) DO work with execute-kbd-macro using the same principle
as simple fields: combine everything in a SINGLE macro.
Pattern:
(funcall-interactively #'my-create) ;; ✅ CORRECT - Combine infix + text + commit in ONE macro (execute-kbd-macro (kbd "- d Full SPC description SPC text C-c C-c")) (execute-kbd-macro (kbd "- A Acceptance SPC criteria C-c C-c"))
Why this works:
- •
- dopens the editor buffer - •
Full SPC description SPC texttypes into that buffer (it's now active) - •
C-c C-ccommits and returns to transient menu - •All happens in single macro execution, so context is preserved
What DOESN'T work (split across macros):
;; ❌ WRONG - Split into separate macros (execute-kbd-macro (kbd "- d")) ; Opens editor (execute-kbd-macro (kbd "My description")) ; Fails! Wrong context (execute-kbd-macro (kbd "C-c C-c")) ; Fails! Wrong context
When you split into multiple execute-kbd-macro calls, each call starts from
the current buffer context, which may not be the editor buffer that was opened.
Summary:
- •✅ Simple minibuffer prompts work (combine key + input + RET)
- •✅ Switches/toggles work (just the key)
- •✅ Multiline editor fields work (combine key + text + C-c C-c)
- •❌ Splitting any interaction across multiple macro calls fails
Comparison to Other Testing Approaches
with-simulated-input package:
- •❌ Adds complexity without clear benefits for transient testing
- •❌ Our pattern (mock transient-args + call-interactively) is simpler
- •✅ execute-kbd-macro works natively in batch mode
Casual project approach:
- •Tests only binding structure (overrides commands with stubs)
- •❌ Doesn't test actual command execution
- •✅ Our approach tests REAL commands with REAL execution
Result: Our execute-kbd-macro approach gives superior coverage!
Version History
- •v1.1.0 (2025-11-19): Added comprehensive testing section with execute-kbd-macro patterns and performance analysis
- •v1.0.0 (2025-11-06): Initial skill created from research of transient, Magit, Forge, and transient-showcase repositories