riplex 0.7.4__tar.gz → 0.8.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (138) hide show
  1. {riplex-0.7.4/src/riplex.egg-info → riplex-0.8.0}/PKG-INFO +1 -1
  2. {riplex-0.7.4 → riplex-0.8.0}/docs/changelog.md +10 -0
  3. {riplex-0.7.4 → riplex-0.8.0/src/riplex.egg-info}/PKG-INFO +1 -1
  4. {riplex-0.7.4 → riplex-0.8.0}/src/riplex.egg-info/SOURCES.txt +1 -0
  5. {riplex-0.7.4 → riplex-0.8.0}/src/riplex_app/screens/done.py +1 -1
  6. {riplex-0.7.4 → riplex-0.8.0}/src/riplex_app/screens/metadata.py +1 -1
  7. {riplex-0.7.4 → riplex-0.8.0}/src/riplex_app/screens/orchestrate_done.py +5 -5
  8. {riplex-0.7.4 → riplex-0.8.0}/src/riplex_app/screens/welcome.py +59 -10
  9. riplex-0.8.0/tests/test_welcome_screen.py +42 -0
  10. {riplex-0.7.4 → riplex-0.8.0}/.github/ISSUE_TEMPLATE/bug_report.yml +0 -0
  11. {riplex-0.7.4 → riplex-0.8.0}/.github/ISSUE_TEMPLATE/crash_report.yml +0 -0
  12. {riplex-0.7.4 → riplex-0.8.0}/.github/agents/riplex.agent.md +0 -0
  13. {riplex-0.7.4 → riplex-0.8.0}/.github/copilot-instructions.md +0 -0
  14. {riplex-0.7.4 → riplex-0.8.0}/.github/workflows/publish.yml +0 -0
  15. {riplex-0.7.4 → riplex-0.8.0}/.github/workflows/release.yml +0 -0
  16. {riplex-0.7.4 → riplex-0.8.0}/.gitignore +0 -0
  17. {riplex-0.7.4 → riplex-0.8.0}/.vscode/settings.json +0 -0
  18. {riplex-0.7.4 → riplex-0.8.0}/CONTRIBUTORS.md +0 -0
  19. {riplex-0.7.4 → riplex-0.8.0}/LICENSE +0 -0
  20. {riplex-0.7.4 → riplex-0.8.0}/README.md +0 -0
  21. {riplex-0.7.4 → riplex-0.8.0}/docs/architecture.md +0 -0
  22. {riplex-0.7.4 → riplex-0.8.0}/docs/cli-guide/lookup.md +0 -0
  23. {riplex-0.7.4 → riplex-0.8.0}/docs/cli-guide/orchestrate.md +0 -0
  24. {riplex-0.7.4 → riplex-0.8.0}/docs/cli-guide/organize.md +0 -0
  25. {riplex-0.7.4 → riplex-0.8.0}/docs/cli-guide/workflow.md +0 -0
  26. {riplex-0.7.4 → riplex-0.8.0}/docs/getting-started/configuration.md +0 -0
  27. {riplex-0.7.4 → riplex-0.8.0}/docs/getting-started/installation.md +0 -0
  28. {riplex-0.7.4 → riplex-0.8.0}/docs/gui-guide/gui-walkthrough.md +0 -0
  29. {riplex-0.7.4 → riplex-0.8.0}/docs/index.md +0 -0
  30. {riplex-0.7.4 → riplex-0.8.0}/docs/naming-rules.md +0 -0
  31. {riplex-0.7.4 → riplex-0.8.0}/docs/reference/cli.md +0 -0
  32. {riplex-0.7.4 → riplex-0.8.0}/docs/troubleshooting.md +0 -0
  33. {riplex-0.7.4 → riplex-0.8.0}/issues/debug-artifacts-consolidation.md +0 -0
  34. {riplex-0.7.4 → riplex-0.8.0}/issues/manual-rip-and-organize-escape-hatch.md +0 -0
  35. {riplex-0.7.4 → riplex-0.8.0}/issues/orchestrate-dvdcompare-fallback.md +0 -0
  36. {riplex-0.7.4 → riplex-0.8.0}/issues/planned-features.md +0 -0
  37. {riplex-0.7.4 → riplex-0.8.0}/mkdocs.yml +0 -0
  38. {riplex-0.7.4 → riplex-0.8.0}/pyproject.toml +0 -0
  39. {riplex-0.7.4 → riplex-0.8.0}/screenshots/0_Rip_Flow_BTTF.gif +0 -0
  40. {riplex-0.7.4 → riplex-0.8.0}/screenshots/1_Welcome_Screen.png +0 -0
  41. {riplex-0.7.4 → riplex-0.8.0}/screenshots/2_Disc_Detection_BTTF.png +0 -0
  42. {riplex-0.7.4 → riplex-0.8.0}/screenshots/3_Metadata_Lookup_BTTF.png +0 -0
  43. {riplex-0.7.4 → riplex-0.8.0}/screenshots/4_Disc_Release_BTTF.png +0 -0
  44. {riplex-0.7.4 → riplex-0.8.0}/screenshots/5_Multi_Disc_Overview_BTTF.png +0 -0
  45. {riplex-0.7.4 → riplex-0.8.0}/screenshots/5_Select_Title_to_RIP_BTTF.png +0 -0
  46. {riplex-0.7.4 → riplex-0.8.0}/setup.cfg +0 -0
  47. {riplex-0.7.4 → riplex-0.8.0}/src/riplex/__init__.py +0 -0
  48. {riplex-0.7.4 → riplex-0.8.0}/src/riplex/cache.py +0 -0
  49. {riplex-0.7.4 → riplex-0.8.0}/src/riplex/config.py +0 -0
  50. {riplex-0.7.4 → riplex-0.8.0}/src/riplex/dedup.py +0 -0
  51. {riplex-0.7.4 → riplex-0.8.0}/src/riplex/detect.py +0 -0
  52. {riplex-0.7.4 → riplex-0.8.0}/src/riplex/disc/__init__.py +0 -0
  53. {riplex-0.7.4 → riplex-0.8.0}/src/riplex/disc/analysis.py +0 -0
  54. {riplex-0.7.4 → riplex-0.8.0}/src/riplex/disc/makemkv.py +0 -0
  55. {riplex-0.7.4 → riplex-0.8.0}/src/riplex/disc/provider.py +0 -0
  56. {riplex-0.7.4 → riplex-0.8.0}/src/riplex/formatter.py +0 -0
  57. {riplex-0.7.4 → riplex-0.8.0}/src/riplex/lookup.py +0 -0
  58. {riplex-0.7.4 → riplex-0.8.0}/src/riplex/manifest.py +0 -0
  59. {riplex-0.7.4 → riplex-0.8.0}/src/riplex/matcher.py +0 -0
  60. {riplex-0.7.4 → riplex-0.8.0}/src/riplex/metadata/__init__.py +0 -0
  61. {riplex-0.7.4 → riplex-0.8.0}/src/riplex/metadata/planner.py +0 -0
  62. {riplex-0.7.4 → riplex-0.8.0}/src/riplex/metadata/provider.py +0 -0
  63. {riplex-0.7.4 → riplex-0.8.0}/src/riplex/metadata/sources/__init__.py +0 -0
  64. {riplex-0.7.4 → riplex-0.8.0}/src/riplex/metadata/sources/tmdb.py +0 -0
  65. {riplex-0.7.4 → riplex-0.8.0}/src/riplex/models.py +0 -0
  66. {riplex-0.7.4 → riplex-0.8.0}/src/riplex/normalize.py +0 -0
  67. {riplex-0.7.4 → riplex-0.8.0}/src/riplex/organizer.py +0 -0
  68. {riplex-0.7.4 → riplex-0.8.0}/src/riplex/scanner.py +0 -0
  69. {riplex-0.7.4 → riplex-0.8.0}/src/riplex/snapshot.py +0 -0
  70. {riplex-0.7.4 → riplex-0.8.0}/src/riplex/splitter.py +0 -0
  71. {riplex-0.7.4 → riplex-0.8.0}/src/riplex/tagger.py +0 -0
  72. {riplex-0.7.4 → riplex-0.8.0}/src/riplex/title.py +0 -0
  73. {riplex-0.7.4 → riplex-0.8.0}/src/riplex/ui.py +0 -0
  74. {riplex-0.7.4 → riplex-0.8.0}/src/riplex/updater.py +0 -0
  75. {riplex-0.7.4 → riplex-0.8.0}/src/riplex.egg-info/dependency_links.txt +0 -0
  76. {riplex-0.7.4 → riplex-0.8.0}/src/riplex.egg-info/entry_points.txt +0 -0
  77. {riplex-0.7.4 → riplex-0.8.0}/src/riplex.egg-info/requires.txt +0 -0
  78. {riplex-0.7.4 → riplex-0.8.0}/src/riplex.egg-info/top_level.txt +0 -0
  79. {riplex-0.7.4 → riplex-0.8.0}/src/riplex_app/__init__.py +0 -0
  80. {riplex-0.7.4 → riplex-0.8.0}/src/riplex_app/bug_report.py +0 -0
  81. {riplex-0.7.4 → riplex-0.8.0}/src/riplex_app/crash_dump.py +0 -0
  82. {riplex-0.7.4 → riplex-0.8.0}/src/riplex_app/keep_awake.py +0 -0
  83. {riplex-0.7.4 → riplex-0.8.0}/src/riplex_app/main.py +0 -0
  84. {riplex-0.7.4 → riplex-0.8.0}/src/riplex_app/screens/__init__.py +0 -0
  85. {riplex-0.7.4 → riplex-0.8.0}/src/riplex_app/screens/disc_detection.py +0 -0
  86. {riplex-0.7.4 → riplex-0.8.0}/src/riplex_app/screens/disc_overview.py +0 -0
  87. {riplex-0.7.4 → riplex-0.8.0}/src/riplex_app/screens/disc_swap.py +0 -0
  88. {riplex-0.7.4 → riplex-0.8.0}/src/riplex_app/screens/folder_picker.py +0 -0
  89. {riplex-0.7.4 → riplex-0.8.0}/src/riplex_app/screens/organize_done.py +0 -0
  90. {riplex-0.7.4 → riplex-0.8.0}/src/riplex_app/screens/organize_preview.py +0 -0
  91. {riplex-0.7.4 → riplex-0.8.0}/src/riplex_app/screens/progress.py +0 -0
  92. {riplex-0.7.4 → riplex-0.8.0}/src/riplex_app/screens/release.py +0 -0
  93. {riplex-0.7.4 → riplex-0.8.0}/src/riplex_app/screens/selection.py +0 -0
  94. {riplex-0.7.4 → riplex-0.8.0}/src/riplex_app/screens/update.py +0 -0
  95. {riplex-0.7.4 → riplex-0.8.0}/src/riplex_cli/__init__.py +0 -0
  96. {riplex-0.7.4 → riplex-0.8.0}/src/riplex_cli/commands/__init__.py +0 -0
  97. {riplex-0.7.4 → riplex-0.8.0}/src/riplex_cli/commands/lookup.py +0 -0
  98. {riplex-0.7.4 → riplex-0.8.0}/src/riplex_cli/commands/orchestrate.py +0 -0
  99. {riplex-0.7.4 → riplex-0.8.0}/src/riplex_cli/commands/organize.py +0 -0
  100. {riplex-0.7.4 → riplex-0.8.0}/src/riplex_cli/commands/rip.py +0 -0
  101. {riplex-0.7.4 → riplex-0.8.0}/src/riplex_cli/commands/setup.py +0 -0
  102. {riplex-0.7.4 → riplex-0.8.0}/src/riplex_cli/formatting.py +0 -0
  103. {riplex-0.7.4 → riplex-0.8.0}/src/riplex_cli/main.py +0 -0
  104. {riplex-0.7.4 → riplex-0.8.0}/tests/__init__.py +0 -0
  105. {riplex-0.7.4 → riplex-0.8.0}/tests/fixtures/chernobyl_disc1.json +0 -0
  106. {riplex-0.7.4 → riplex-0.8.0}/tests/fixtures/makemkvcon_frozen_planet_ii_d2.txt +0 -0
  107. {riplex-0.7.4 → riplex-0.8.0}/tests/fixtures/makemkvcon_list.txt +0 -0
  108. {riplex-0.7.4 → riplex-0.8.0}/tests/snapshots/Batman Begins.snapshot.json +0 -0
  109. {riplex-0.7.4 → riplex-0.8.0}/tests/snapshots/Blade Runner (Blu-ray 4k).snapshot.json +0 -0
  110. {riplex-0.7.4 → riplex-0.8.0}/tests/snapshots/Blade Runner The Final Cut.snapshot.json +0 -0
  111. {riplex-0.7.4 → riplex-0.8.0}/tests/snapshots/Seven Worlds One Planet.snapshot.json +0 -0
  112. {riplex-0.7.4 → riplex-0.8.0}/tests/snapshots/The Dark Knight Rises.snapshot.json +0 -0
  113. {riplex-0.7.4 → riplex-0.8.0}/tests/snapshots/The Dark Knight.snapshot.json +0 -0
  114. {riplex-0.7.4 → riplex-0.8.0}/tests/snapshots/Waterworld.snapshot.json +0 -0
  115. {riplex-0.7.4 → riplex-0.8.0}/tests/test_cache.py +0 -0
  116. {riplex-0.7.4 → riplex-0.8.0}/tests/test_cli_utils.py +0 -0
  117. {riplex-0.7.4 → riplex-0.8.0}/tests/test_config.py +0 -0
  118. {riplex-0.7.4 → riplex-0.8.0}/tests/test_dedup.py +0 -0
  119. {riplex-0.7.4 → riplex-0.8.0}/tests/test_detect.py +0 -0
  120. {riplex-0.7.4 → riplex-0.8.0}/tests/test_disc_analysis.py +0 -0
  121. {riplex-0.7.4 → riplex-0.8.0}/tests/test_disc_detection_screen.py +0 -0
  122. {riplex-0.7.4 → riplex-0.8.0}/tests/test_disc_fixtures.py +0 -0
  123. {riplex-0.7.4 → riplex-0.8.0}/tests/test_disc_provider.py +0 -0
  124. {riplex-0.7.4 → riplex-0.8.0}/tests/test_formatter.py +0 -0
  125. {riplex-0.7.4 → riplex-0.8.0}/tests/test_lookup.py +0 -0
  126. {riplex-0.7.4 → riplex-0.8.0}/tests/test_makemkv.py +0 -0
  127. {riplex-0.7.4 → riplex-0.8.0}/tests/test_matcher.py +0 -0
  128. {riplex-0.7.4 → riplex-0.8.0}/tests/test_normalize.py +0 -0
  129. {riplex-0.7.4 → riplex-0.8.0}/tests/test_organizer.py +0 -0
  130. {riplex-0.7.4 → riplex-0.8.0}/tests/test_planner.py +0 -0
  131. {riplex-0.7.4 → riplex-0.8.0}/tests/test_release_screen.py +0 -0
  132. {riplex-0.7.4 → riplex-0.8.0}/tests/test_rip_guide.py +0 -0
  133. {riplex-0.7.4 → riplex-0.8.0}/tests/test_scanner.py +0 -0
  134. {riplex-0.7.4 → riplex-0.8.0}/tests/test_snapshot.py +0 -0
  135. {riplex-0.7.4 → riplex-0.8.0}/tests/test_splitter.py +0 -0
  136. {riplex-0.7.4 → riplex-0.8.0}/tests/test_tagger.py +0 -0
  137. {riplex-0.7.4 → riplex-0.8.0}/tests/test_ui.py +0 -0
  138. {riplex-0.7.4 → riplex-0.8.0}/tests/test_updater.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: riplex
3
- Version: 0.7.4
3
+ Version: 0.8.0
4
4
  Summary: Automates the tedious manual work around MakeMKV: figuring out what to rip, which MKV files are actually what, and organizing everything into Plex-compatible folder structures.
5
5
  License: MIT
6
6
  Requires-Python: >=3.11
@@ -4,6 +4,16 @@ All notable changes to the riplex documentation are recorded here.
4
4
 
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/).
6
6
 
7
+ ## v0.8.0 — 2026-06-12
8
+
9
+ ### Added
10
+
11
+ - **GUI: editable settings after first-run setup.** The welcome screen now exposes an **Edit Settings** button after configuration is complete, so users can update the TMDb API key, media library root, MakeMKV rip output folder, and optional archive folder without re-running setup from the command line.
12
+
13
+ ### Changed
14
+
15
+ - **GUI: less Plex-specific library wording.** User-facing destination copy now refers to a general media library while retaining **Plex-compatible naming** where the current folder/file convention is being described.
16
+
7
17
  ## v0.7.4 — 2026-06-10
8
18
 
9
19
  ### Fixed
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: riplex
3
- Version: 0.7.4
3
+ Version: 0.8.0
4
4
  Summary: Automates the tedious manual work around MakeMKV: figuring out what to rip, which MKV files are actually what, and organizing everything into Plex-compatible folder structures.
5
5
  License: MIT
6
6
  Requires-Python: >=3.11
@@ -123,6 +123,7 @@ tests/test_splitter.py
123
123
  tests/test_tagger.py
124
124
  tests/test_ui.py
125
125
  tests/test_updater.py
126
+ tests/test_welcome_screen.py
126
127
  tests/fixtures/chernobyl_disc1.json
127
128
  tests/fixtures/makemkvcon_frozen_planet_ii_d2.txt
128
129
  tests/fixtures/makemkvcon_list.txt
@@ -87,7 +87,7 @@ class DoneScreen:
87
87
 
88
88
  no_metadata_hint = ft.Text(
89
89
  "Ripped without metadata — use 'Organize Rips' from the home screen "
90
- "when TMDb is available to move these files into your Plex library.",
90
+ "when TMDb is available to move these files into your media library.",
91
91
  size=12,
92
92
  color=ft.Colors.ORANGE,
93
93
  ) if tmdb_match is None and len(successful) > 0 else ft.Container()
@@ -149,7 +149,7 @@ class MetadataScreen:
149
149
  [
150
150
  ft.Text("Metadata Lookup", size=24, weight=ft.FontWeight.BOLD),
151
151
  ft.Text(
152
- "TMDb is needed for canonical titles, years, and Plex folder "
152
+ "TMDb is needed for canonical titles, years, and media-library folder "
153
153
  "structure. Without it, you can still rip the disc and organize later.",
154
154
  size=13,
155
155
  color=ft.Colors.GREY_500,
@@ -17,7 +17,7 @@ class OrchestrateDoneScreen:
17
17
  """Shown after all discs in the orchestrate queue have been ripped.
18
18
 
19
19
  Displays a summary of all disc rips and offers to organize the
20
- ripped files into Plex-compatible folder structure.
20
+ ripped files into a Plex-compatible folder structure.
21
21
  """
22
22
 
23
23
  def __init__(self, app):
@@ -108,7 +108,7 @@ class OrchestrateDoneScreen:
108
108
  on_click=self._open_folder,
109
109
  )
110
110
  organize_btn = ft.ElevatedButton(
111
- "Organize into Plex",
111
+ "Organize into Library",
112
112
  icon=ft.Icons.DRIVE_FILE_MOVE,
113
113
  on_click=self._organize,
114
114
  style=ft.ButtonStyle(
@@ -136,8 +136,8 @@ class OrchestrateDoneScreen:
136
136
  ft.Column(disc_rows, spacing=6),
137
137
  ft.Container(height=10),
138
138
  ft.Text(
139
- "All selected discs have been ripped. Click 'Organize into Plex' "
140
- "to match files to metadata and move them into your Plex library.",
139
+ "All selected discs have been ripped. Click 'Organize into Library' "
140
+ "to match files to metadata and move them into your media library.",
141
141
  size=13,
142
142
  color=ft.Colors.GREY_500,
143
143
  ),
@@ -202,7 +202,7 @@ class OrchestrateDoneScreen:
202
202
  self._organize_status.value = f"Error: {exc}"
203
203
  self._organize_status.color = ft.Colors.RED
204
204
  e.control.disabled = False
205
- e.control.text = "Organize into Plex"
205
+ e.control.text = "Organize into Library"
206
206
  self.app.page.update()
207
207
 
208
208
  threading.Thread(target=_do_organize, daemon=True).start()
@@ -16,13 +16,30 @@ from riplex.updater import check_for_update, get_current_version
16
16
  log = logging.getLogger(__name__)
17
17
 
18
18
 
19
+ def has_complete_config(config: dict) -> bool:
20
+ """Return True when required GUI setup values are present."""
21
+ return bool(
22
+ config
23
+ and config.get("tmdb_api_key")
24
+ and config.get("output_root")
25
+ and config.get("rip_output")
26
+ )
27
+
28
+
29
+ def should_show_setup(config: dict, state: dict) -> bool:
30
+ """Return True when the welcome screen should show setup fields."""
31
+ return not has_complete_config(config) or bool(state.get("_show_setup"))
32
+
33
+
19
34
  class WelcomeScreen:
20
35
  def __init__(self, app):
21
36
  self.app = app
22
37
 
23
38
  def build(self) -> ft.Control:
24
39
  config = load_config()
25
- has_config = bool(config and config.get("tmdb_api_key"))
40
+ has_api_key = bool(config and config.get("tmdb_api_key"))
41
+ has_config = has_complete_config(config)
42
+ show_setup = should_show_setup(config, self.app.state)
26
43
  has_makemkv = find_makemkvcon() is not None
27
44
  has_ffprobe = find_ffprobe() is not None
28
45
  from riplex.splitter import find_mkvmerge
@@ -31,7 +48,7 @@ class WelcomeScreen:
31
48
  # Status indicators
32
49
  checks = [
33
50
  ("Config file", has_config),
34
- ("TMDb API key", has_config),
51
+ ("TMDb API key", has_api_key),
35
52
  ("makemkvcon", has_makemkv),
36
53
  ("ffprobe", has_ffprobe),
37
54
  ("mkvmerge", has_mkvmerge),
@@ -102,7 +119,7 @@ class WelcomeScreen:
102
119
  can_rip = all(ok for _, ok in checks)
103
120
  can_organize = has_config and has_ffprobe
104
121
 
105
- # Setup fields (shown if config missing)
122
+ # Setup fields (shown if config missing or user chooses to edit)
106
123
  self.api_key_field = ft.TextField(
107
124
  label="TMDb API key",
108
125
  value=config.get("tmdb_api_key", ""),
@@ -111,7 +128,7 @@ class WelcomeScreen:
111
128
  expand=True,
112
129
  )
113
130
  self.output_root_field = ft.TextField(
114
- label="Plex library root",
131
+ label="Media library root",
115
132
  value=config.get("output_root", ""),
116
133
  expand=True,
117
134
  )
@@ -155,8 +172,9 @@ class WelcomeScreen:
155
172
  ft.Container(height=8),
156
173
  _make_browse_row(self.output_root_field),
157
174
  ft.Text(
158
- "Your Plex media library root. Organized files will be placed "
159
- "into Movies/ and TV Shows/ subfolders here.",
175
+ "Your media library root. Organized files will be placed "
176
+ "into Movies/ and TV Shows/ subfolders here using "
177
+ "Plex-compatible naming.",
160
178
  size=11,
161
179
  color=ft.Colors.GREY_600,
162
180
  ),
@@ -177,10 +195,20 @@ class WelcomeScreen:
177
195
  color=ft.Colors.GREY_600,
178
196
  ),
179
197
  ft.Container(height=8),
180
- ft.ElevatedButton("Save Config", on_click=self._save_config),
198
+ ft.Row(
199
+ [
200
+ ft.ElevatedButton("Save Config", on_click=self._save_config),
201
+ ft.TextButton(
202
+ "Cancel",
203
+ on_click=self._hide_setup,
204
+ visible=has_config,
205
+ ),
206
+ ],
207
+ spacing=8,
208
+ ),
181
209
  ],
182
210
  spacing=4,
183
- visible=not has_config,
211
+ visible=show_setup,
184
212
  )
185
213
 
186
214
  # Workflow buttons
@@ -190,7 +218,7 @@ class WelcomeScreen:
190
218
  on_click=self._start_rip,
191
219
  disabled=not can_rip,
192
220
  style=ft.ButtonStyle(padding=ft.Padding(left=30, top=15, right=30, bottom=15)),
193
- tooltip="Detect a disc, look up metadata, rip selected titles, and organize into Plex.",
221
+ tooltip="Detect a disc, look up metadata, rip selected titles, and organize into your media library.",
194
222
  )
195
223
  organize_button = ft.ElevatedButton(
196
224
  "Organize Rips",
@@ -228,7 +256,7 @@ class WelcomeScreen:
228
256
  ),
229
257
  self.update_banner,
230
258
  ft.Text(
231
- "Rip physical discs and organize into Plex-compatible libraries.",
259
+ "Rip physical discs and organize them into a media library with Plex-compatible naming.",
232
260
  size=14,
233
261
  color=ft.Colors.GREY_400,
234
262
  ),
@@ -244,6 +272,16 @@ class WelcomeScreen:
244
272
  ft.Column(status_rows, spacing=4),
245
273
  install_section,
246
274
  ft.Container(height=10),
275
+ ft.Row(
276
+ [
277
+ ft.OutlinedButton(
278
+ "Edit Settings",
279
+ icon=ft.Icons.SETTINGS,
280
+ on_click=self._show_setup,
281
+ visible=has_config and not show_setup,
282
+ ),
283
+ ],
284
+ ),
247
285
  setup_section,
248
286
  ft.Container(expand=True),
249
287
  ft.Text("What would you like to do?", size=16, weight=ft.FontWeight.BOLD),
@@ -645,6 +683,17 @@ class WelcomeScreen:
645
683
  )
646
684
 
647
685
  # Refresh the screen
686
+ self.app.state.pop("_show_setup", None)
687
+ self.app.navigate("welcome")
688
+
689
+ def _show_setup(self, e):
690
+ """Reveal config fields for editing after first-run setup."""
691
+ self.app.state["_show_setup"] = True
692
+ self.app.navigate("welcome")
693
+
694
+ def _hide_setup(self, e):
695
+ """Hide config fields without saving changes."""
696
+ self.app.state.pop("_show_setup", None)
648
697
  self.app.navigate("welcome")
649
698
 
650
699
  def _start_rip(self, e):
@@ -0,0 +1,42 @@
1
+ from riplex_app.screens.welcome import has_complete_config, should_show_setup
2
+
3
+
4
+ class TestHasCompleteConfig:
5
+ def test_requires_api_key_output_root_and_rip_output(self):
6
+ assert has_complete_config({}) is False
7
+ assert has_complete_config({"tmdb_api_key": "key"}) is False
8
+ assert has_complete_config({"tmdb_api_key": "key", "output_root": "E:/Media"}) is False
9
+ assert has_complete_config({
10
+ "tmdb_api_key": "key",
11
+ "output_root": "E:/Media",
12
+ "rip_output": "E:/Media/_MakeMKV",
13
+ }) is True
14
+
15
+ def test_archive_root_is_optional(self):
16
+ assert has_complete_config({
17
+ "tmdb_api_key": "key",
18
+ "output_root": "E:/Media",
19
+ "rip_output": "E:/Media/_MakeMKV",
20
+ "archive_root": "",
21
+ }) is True
22
+
23
+
24
+ class TestShouldShowSetup:
25
+ def test_shows_setup_for_incomplete_config(self):
26
+ assert should_show_setup({"tmdb_api_key": "key"}, {}) is True
27
+
28
+ def test_hides_setup_for_complete_config(self):
29
+ config = {
30
+ "tmdb_api_key": "key",
31
+ "output_root": "E:/Media",
32
+ "rip_output": "E:/Media/_MakeMKV",
33
+ }
34
+ assert should_show_setup(config, {}) is False
35
+
36
+ def test_can_force_show_setup_for_editing(self):
37
+ config = {
38
+ "tmdb_api_key": "key",
39
+ "output_root": "E:/Media",
40
+ "rip_output": "E:/Media/_MakeMKV",
41
+ }
42
+ assert should_show_setup(config, {"_show_setup": True}) is True
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes