Repository: Goob-Station/Goob-Station
Pull request #1969: Autodoc buff
Author: login: JohnOakman
Labels: name: Changes: UI
name: size/M
description: Denotes a PR that changes 100-999 lines.
Created: 2025-03-09T18:23:26Z | Merged: 2025-03-11T21:07:09Z

## About the PR
Buffed autodoc.

## Why / Balance
I barely see the autodoc being used EVER. 
And some QOL features were required.

## Technical details
I have only touched the autodoc files in _shitmed, shared and client.
Changed autodoc surgery speed from 0.5 to 1.
Added import/Export program to yaml.

## Media
![image](https://github.com/user-attachments/assets/f154a322-eebb-4e26-8521-92a4a11e9ef2)
Exporting program
![image](https://github.com/user-attachments/assets/cd7f5f1b-1dd0-4605-881c-1f415f3ada21)
Importing program
![image](https://github.com/user-attachments/assets/0d2a507b-a713-4095-8a58-ae7a40afa023)
Imported program

Yes the text has been properly set, these screenshots were done after I filled the Locale file.

## Requirements
<!-- Confirm the following by placing an X in the brackets [X]: -->
- [X] I have read and am following the [Pull Request and Changelog Guidelines](https://docs.spacestation14.com/en/general-development/codebase-info/pull-request-guidelines.html).
- [X] I have added media to this PR or it does not require an ingame showcase.
<!-- You should understand that not following the above may get your PR closed at maintainer’s discretion -->

**Changelog**
<!-- Add a Changelog entry to make players aware of new features or changes that could affect gameplay.
Make sure to read the guidelines and take this Changelog template out of the comment block in order for it to show up.
Changelog must have a :cl: symbol, so the bot recognizes the changes and adds them to the game's changelog. -->
:cl:
- add: You can now Import/Export programs in the autodoc!
- tweak: Autodoc speed set to 1 instead of 0.5! (actually useful now)


Diff:
diff --git a/Content.Client/_Shitmed/Autodoc/AutodocBoundUserInterface.cs b/Content.Client/_Shitmed/Autodoc/AutodocBoundUserInterface.cs
index 14d03b5fe669..75edb80a9a4b 100644
--- a/Content.Client/_Shitmed/Autodoc/AutodocBoundUserInterface.cs
+++ b/Content.Client/_Shitmed/Autodoc/AutodocBoundUserInterface.cs
@@ -22,6 +22,8 @@ public AutodocBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKe
         _window.OnAddStep += (program, step, index) => SendMessage(new AutodocAddStepMessage(program, step, index));
         _window.OnRemoveStep += (program, stepIndex) => SendMessage(new AutodocRemoveStepMessage(program, stepIndex));
 
+        _window.OnImportProgram += (program) => SendMessage(new AutodocImportProgramMessage(program));
+
         _window.OnStart += program => SendMessage(new AutodocStartMessage(program));
         _window.OnStop += () => SendMessage(new AutodocStopMessage());
 
diff --git a/Content.Client/_Shitmed/Autodoc/AutodocProgramWindow.xaml b/Content.Client/_Shitmed/Autodoc/AutodocProgramWindow.xaml
index afaed8b50cba..410fe8d12a4b 100644
--- a/Content.Client/_Shitmed/Autodoc/AutodocProgramWindow.xaml
+++ b/Content.Client/_Shitmed/Autodoc/AutodocProgramWindow.xaml
@@ -11,6 +11,7 @@
             <Button Name="AddStepButton" Text="{Loc 'autodoc-add-step'}"/>
             <Button Name="RemoveStepButton" StyleClasses="Caution" Text="{Loc 'autodoc-remove-step'}" Disabled="True"/>
             <Button Name="StartButton" StyleClasses="Caution" Text="{Loc 'autodoc-start-program'}"/>
+            <Button Name="ExportProgramButton" Text="{Loc 'autodoc-export-program'}"/>
         </BoxContainer>
         <ScrollContainer HorizontalExpand="True" VerticalExpand="True">
               <ItemList Name="Steps"/> <!-- Set to Program.Steps -->
diff --git a/Content.Client/_Shitmed/Autodoc/AutodocProgramWindow.xaml.cs b/Content.Client/_Shitmed/Autodoc/AutodocProgramWindow.xaml.cs
index 9b7fab1b761d..0fce5cf8ba39 100644
--- a/Content.Client/_Shitmed/Autodoc/AutodocProgramWindow.xaml.cs
+++ b/Content.Client/_Shitmed/Autodoc/AutodocProgramWindow.xaml.cs
@@ -3,8 +3,11 @@
 using Content.Shared._Shitmed.Autodoc.Components;
 using Content.Shared._Shitmed.Autodoc.Systems;
 using Robust.Client.AutoGenerated;
+using Robust.Client.UserInterface;
 using Robust.Client.UserInterface.XAML;
+using Robust.Shared.Serialization.Manager;
 using Robust.Shared.Timing;
+using System.IO;
 
 namespace Content.Client._Shitmed.Autodoc;
 
@@ -12,6 +15,7 @@ namespace Content.Client._Shitmed.Autodoc;
 public sealed partial class AutodocProgramWindow : FancyWindow
 {
     [Dependency] private readonly IEntityManager _entMan = default!;
+    [Dependency] private readonly IFileDialogManager _dialogManager = default!;
     private SharedAutodocSystem _autodoc = default!;
 
     public event Action? OnToggleSafety;
@@ -20,6 +24,7 @@ public sealed partial class AutodocProgramWindow : FancyWindow
     public event Action<int>? OnRemoveStep;
     public event Action? OnStart;
 
+
     private EntityUid _owner;
     private AutodocProgram _program;
     private int _steps;
@@ -98,6 +103,11 @@ public AutodocProgramWindow(EntityUid owner, AutodocProgram program)
             Close();
         };
 
+        ExportProgramButton.OnPressed += _ =>
+        {
+            ExportProgram();
+        };
+
         Steps.OnItemSelected += args =>
         {
             _selected = args.ItemIndex;
@@ -113,6 +123,32 @@ public AutodocProgramWindow(EntityUid owner, AutodocProgram program)
         UpdateSafety();
     }
 
+    private async void ExportProgram()
+    {
+        var file = await _dialogManager.SaveFile(new FileDialogFilters(new FileDialogFilters.Group("yml")));
+
+        if (file == null)
+            return;
+
+        try
+        {
+            var serializationManager = IoCManager.Resolve<ISerializationManager>();
+            var node = serializationManager.WriteValue(_program.GetType(), _program);
+            await using var writer = new StreamWriter(file.Value.fileStream);
+            node.Write(writer);
+        }
+        catch (Exception exc)
+        {
+            ILogManager logManager = IoCManager.Resolve<ILogManager>(); // Using automatic dependancy doesn't work here
+            ISawmill sawmill = logManager.GetSawmill("autodoc-ui");
+            sawmill.Error($"Error when exporting program\n{exc.StackTrace}");
+        }
+        finally
+        {
+            await file.Value.fileStream.DisposeAsync();
+        }
+    }
+
     private void UpdateSafety()
     {
         var safety = !_program.SkipFailed;
diff --git a/Content.Client/_Shitmed/Autodoc/AutodocWindow.xaml b/Content.Client/_Shitmed/Autodoc/AutodocWindow.xaml
index d84a397adffe..e3cd9c2df93d 100644
--- a/Content.Client/_Shitmed/Autodoc/AutodocWindow.xaml
+++ b/Content.Client/_Shitmed/Autodoc/AutodocWindow.xaml
@@ -8,6 +8,7 @@
         </ScrollContainer>
         <BoxContainer Orientation="Horizontal" Margin="5 5 5 5" Align="Center">
             <Button Name="CreateProgramButton" StyleClasses="OpenRight" Text="{Loc 'autodoc-create-program'}"/>
+            <Button Name="ImportProgramButton" StyleClasses="OpenBoth" Text="{Loc 'autodoc-import-program'}"/>
             <Button Name="AbortButton" StyleClasses="OpenLeft" Text="{Loc 'autodoc-abort-program'}" Disabled="True"/>
         </BoxContainer>
     </BoxContainer>
diff --git a/Content.Client/_Shitmed/Autodoc/AutodocWindow.xaml.cs b/Content.Client/_Shitmed/Autodoc/AutodocWindow.xaml.cs
index b606e22b5229..4a66474918bf 100644
--- a/Content.Client/_Shitmed/Autodoc/AutodocWindow.xaml.cs
+++ b/Content.Client/_Shitmed/Autodoc/AutodocWindow.xaml.cs
@@ -8,6 +8,12 @@
 using Robust.Client.UserInterface.Controls;
 using Robust.Client.UserInterface.XAML;
 using Robust.Shared.Timing;
+using Robust.Client.UserInterface;
+using Robust.Shared.Serialization.Manager;
+using Robust.Shared.Utility;
+using System.IO;
+using YamlDotNet.RepresentationModel;
+using Robust.Shared.Serialization.Markdown;
 
 namespace Content.Client._Shitmed.Autodoc;
 
@@ -29,6 +35,7 @@ public sealed partial class AutodocWindow : FancyWindow
     public event Action<int, int>? OnRemoveStep;
     public event Action<int>? OnStart;
     public event Action? OnStop;
+    public event Action<AutodocProgram>? OnImportProgram;
 
     private DialogWindow? _dialog;
     private AutodocProgramWindow? _currentProgram;
@@ -49,6 +56,11 @@ public AutodocWindow(EntityUid owner, IEntityManager entMan, IPlayerManager play
             _currentProgram?.Close();
         };
 
+        ImportProgramButton.OnPressed += _ =>
+        {
+            ImportProgram();
+        };
+
         CreateProgramButton.OnPressed += _ =>
         {
             if (_dialog != null)
@@ -140,6 +152,33 @@ private void UpdatePrograms()
         }
     }
 
+    private async void ImportProgram()
+    {
+        var dialogManager = IoCManager.Resolve<IFileDialogManager>();
+        var file = await dialogManager.OpenFile(new FileDialogFilters(new FileDialogFilters.Group("yml")));
+
+        if (file == null)
+            return;
+
+        try
+        {
+            var serializationManager = IoCManager.Resolve<ISerializationManager>();
+            using var reader = new StreamReader(file, EncodingHelpers.UTF8);
+            var yamlStream = new YamlStream();
+            yamlStream.Load(reader);
+            var root = yamlStream.Documents[0].RootNode;
+            var program = serializationManager.Read<AutodocProgram>(root.ToDataNode(), notNullableOverride: true);
+            OnImportProgram?.Invoke(program);
+        }
+        catch (Exception exc)
+        {
+            ILogManager logManager = IoCManager.Resolve<ILogManager>(); // Using automatic dependancy doesn't work here
+            ISawmill sawmill = logManager.GetSawmill("autodoc-ui");
+            sawmill.Error($"Error when importing program\n{exc.StackTrace}");
+        }
+
+    }
+
     private void OpenProgram(int index)
     {
         if (!_entMan.TryGetComponent<AutodocComponent>(_owner, out var comp))
diff --git a/Content.Shared/_Shitmed/Autodoc/AutodocUI.cs b/Content.Shared/_Shitmed/Autodoc/AutodocUI.cs
index a963243798f1..8c9cbdde45df 100644
--- a/Content.Shared/_Shitmed/Autodoc/AutodocUI.cs
+++ b/Content.Shared/_Shitmed/Autodoc/AutodocUI.cs
@@ -47,5 +47,11 @@ public sealed class AutodocStartMessage(int program) : BoundUserInterfaceMessage
     public readonly int Program = program;
 }
 
+[Serializable, NetSerializable]
+public sealed class AutodocImportProgramMessage(AutodocProgram program) : BoundUserInterfaceMessage
+{
+    public readonly AutodocProgram Program = program;
+}
+
 [Serializable, NetSerializable]
 public sealed class AutodocStopMessage : BoundUserInterfaceMessage;
diff --git a/Content.Shared/_Shitmed/Autodoc/Systems/SharedAutodocSystem.cs b/Content.Shared/_Shitmed/Autodoc/Systems/SharedAutodocSystem.cs
index 9d4cbcc63b89..9b86f1e71b1a 100644
--- a/Content.Shared/_Shitmed/Autodoc/Systems/SharedAutodocSystem.cs
+++ b/Content.Shared/_Shitmed/Autodoc/Systems/SharedAutodocSystem.cs
@@ -14,9 +14,11 @@
 using Content.Shared.Mobs.Systems;
 using Content.Shared.Storage;
 using Content.Shared.Storage.EntitySystems;
+using Content.Shared.VoiceMask;
 using Content.Shared.Whitelist;
 using Robust.Shared.Prototypes;
 using Robust.Shared.Timing;
+using System;
 using System.Linq;
 
 namespace Content.Shared._Shitmed.Autodoc.Systems;
@@ -49,6 +51,7 @@ public override void Initialize()
             s.Event<AutodocRemoveStepMessage>(OnRemoveStep);
             s.Event<AutodocStartMessage>(OnStart);
             s.Event<AutodocStopMessage>(OnStop);
+            s.Event<AutodocImportProgramMessage>(OnImportProgram);
         });
 
         SubscribeLocalEvent<ActiveAutodocComponent, SurgeryStepEvent>(OnSurgeryStep);
@@ -128,6 +131,11 @@ private void OnStop(Entity<AutodocComponent> ent, ref AutodocStopMessage args)
         RemComp<ActiveAutodocComponent>(ent);
     }
 
+    private void OnImportProgram(Entity<AutodocComponent> ent, ref AutodocImportProgramMessage args)
+    {
+        ImportProgram(ent, args.Program, args.Actor);
+    }
+
     #endregion
 
     private void OnSurgeryStep(Entity<ActiveAutodocComponent> ent, ref SurgeryStepEvent args)
@@ -318,6 +326,29 @@ public bool IsAwake(EntityUid uid)
         return _mobState.IsAlive(uid) && !HasComp<SleepingComponent>(uid);
     }
 
+    /// <summary>
+    /// Creates a new program and populates it using another AutodocProgram.
+    /// Will return false on fail. True on success.
+    /// </summary>
+    public bool ImportProgram(Entity<AutodocComponent> ent, AutodocProgram program, EntityUid user)
+    {
+        var idx = CreateProgram(ent, program.Title);
+
+        if (!idx.HasValue)
+            return false;
+
+        for (int key = 0; key < program.Steps.Count; ++key)
+        {
+            if (!program.Steps[key].Validate(ent, this))
+            {
+                Log.Warning($"User {ToPrettyString(user)} tried to add an invalid autodoc step!");
+                return false;
+            }
+            AddStep(ent, idx.Value, program.Steps[key], key, user);
+        }
+        return true;
+    }
+
     /// <summary>
     /// Create a blank program and return the index to it.
     /// Programs cannot be created while operating or if there are too many, in which case it will return null.
diff --git a/Resources/Locale/en-US/_Shitmed/autodoc.ftl b/Resources/Locale/en-US/_Shitmed/autodoc.ftl
index c42c64f321fb..1d62a979e34d 100644
--- a/Resources/Locale/en-US/_Shitmed/autodoc.ftl
+++ b/Resources/Locale/en-US/_Shitmed/autodoc.ftl
@@ -48,6 +48,10 @@ autodoc-add-step = ADD STEP 添加一步
 autodoc-remove-step = REMOVE STEP 删除一步
 # Chinese text translates approximately to "Launch the Glorious People's Project" (as with all these long sentences, im not super sure on the translation making sense)
 autodoc-start-program = START PROGRAM 发起光荣人民计划
+# Chinese text translates approximately to "import program")
+autodoc-import-program = IMPORT PROGRAM 进口计划
+# Chinese text translates approximately to "import program")
+autodoc-export-program = EXPORT PROGRAM 出口计划
 
 # Chinese text translates approximately to "Start Surgical Operation"
 autodoc-add-step-surgery = PERFORM SURGERY 开始手术
diff --git a/Resources/Prototypes/_Shitmed/Entities/Structures/Machines/autodoc.yml b/Resources/Prototypes/_Shitmed/Entities/Structures/Machines/autodoc.yml
index 809aeb754c01..8f6779c2d402 100644
--- a/Resources/Prototypes/_Shitmed/Entities/Structures/Machines/autodoc.yml
+++ b/Resources/Prototypes/_Shitmed/Entities/Structures/Machines/autodoc.yml
@@ -79,7 +79,7 @@
     grid:
     - 0,0,3,3
   - type: SurgerySpeedModifier
-    speedModifier: 0.5 # on par with a surgeon using normal tools
+    speedModifier: 1
   - type: GuideHelp
     guides: [ Autodoc ]
 


Reviews:
user: login: coderabbitai[bot]
body: **Actionable comments posted: 1**

<details>
<summary>🧹 Nitpick comments (5)</summary><blockquote>

<details>
<summary>Resources/Locale/en-US/_Shitmed/autodoc.ftl (1)</summary><blockquote>

`51-54`: **THY TRANSLATION SCROLLS CONTAIN A MINOR FALSEHOOD!**

The localization runes have been inscribed with proper power, but I detect a subtle illusion in thy comments! The second comment falsely claims to translate to "import program" when it should speak of "export program" instead!

CORRECT THY ARCANE COMMENT TO AVOID CONFUSING FUTURE WIZARDS!

```diff
 # Chinese text translates approximately to "import program")
 autodoc-import-program = IMPORT PROGRAM 进口计划
-# Chinese text translates approximately to "import program")
+# Chinese text translates approximately to "export program")
 autodoc-export-program = EXPORT PROGRAM 出口计划
```

</blockquote></details>
<details>
<summary>Content.Client/_Shitmed/Autodoc/AutodocWindow.xaml.cs (1)</summary><blockquote>

`155-179`: **POWERFUL IMPORT ENCHANTMENT HAS BEEN CAST!**

This asynchronous spell of importation is most powerful! It summons a file dialog, reads the YAML scrolls of knowledge, and transmutes them into AutodocProgram entities!

HOWEVER! The spell lacks a visual incantation to alert users when the ritual fails! They shall be left in darkness, unaware of magical failures!

Enhance thy spell with this improved incantation:

```diff
catch (Exception exc)
{
    ILogManager logManager = IoCManager.Resolve<ILogManager>(); // Using automatic dependancy doesn't work here
    ISawmill sawmill = logManager.GetSawmill("autodoc-ui");
    sawmill.Error($"Error when importing program\n{exc.StackTrace}");
+   IUserInterfaceManager uiManager = IoCManager.Resolve<IUserInterfaceManager>();
+   uiManager.PopupMessage(UserInterfaceManager.WindowRoot, "ERROR! IMPORT RITUAL HAS FAILED!");
}
```

</blockquote></details>
<details>
<summary>Content.Shared/_Shitmed/Autodoc/Systems/SharedAutodocSystem.cs (1)</summary><blockquote>

`330-351`: **A MIGHTY PROGRAM IMPORTATION RITUAL HAS BEEN FORGED!**

This powerful method creates a new program based on an existing one, validating each step to ensure only valid magical incantations are included. THE ERROR HANDLING IS ROBUST AND THE FUNCTION RETURNS APPROPRIATE STATUS!

HOWEVER! There lurks a potential danger with the validation. If a program contains invalid steps, the function will abort at the first one encountered, leaving a partially created program in the system!

Consider this more powerful alternative that validates ALL steps before creating the program:

```diff
public bool ImportProgram(Entity<AutodocComponent> ent, AutodocProgram program, EntityUid user)
{
+   // VALIDATE ALL STEPS BEFORE PROCEEDING WITH THE RITUAL
+   for (int key = 0; key < program.Steps.Count; ++key)
+   {
+       if (!program.Steps[key].Validate(ent, this))
+       {
+           Log.Warning($"User {ToPrettyString(user)} tried to add an invalid autodoc step!");
+           return false;
+       }
+   }
+
    var idx = CreateProgram(ent, program.Title);

    if (!idx.HasValue)
        return false;

    for (int key = 0; key < program.Steps.Count; ++key)
    {
-       if (!program.Steps[key].Validate(ent, this))
-       {
-           Log.Warning($"User {ToPrettyString(user)} tried to add an invalid autodoc step!");
-           return false;
-       }
        AddStep(ent, idx.Value, program.Steps[key], key, user);
    }
    return true;
}
```

</blockquote></details>
<details>
<summary>Content.Client/_Shitmed/Autodoc/AutodocProgramWindow.xaml.cs (2)</summary><blockquote>

`128-128`: **THE FILE EXTENSION RUNE IS WEAK!**

Thy file filter uses merely "yml" but the stronger enchantment would be to include both "yml" AND "yaml" extensions! MORTALS USE BOTH FORMS OF THE ANCIENT YAML SCROLL FORMAT!


```diff
-        var file = await _dialogManager.SaveFile(new FileDialogFilters(new FileDialogFilters.Group("yml")));
+        var file = await _dialogManager.SaveFile(new FileDialogFilters(new FileDialogFilters.Group("yml", "yaml")));
```

---

`135-139`: **THY SERIALIZATION MAGIC LACKS PROPER DEFENSE AGAINST DARK FORCES!**

The writing of YAML to parchment requires more explicit error handling! What if thy serializationManager fails to write thy _program? What if thy file path leads to forbidden realms? THOU MUST CHECK FOR NULL NODES!


```diff
             var serializationManager = IoCManager.Resolve<ISerializationManager>();
             var node = serializationManager.WriteValue(_program.GetType(), _program);
+            if (node == null)
+            {
+                IoCManager.Resolve<ILogManager>().GetSawmill("autodoc-ui").Error("Failed to serialize program: null node returned");
+                return;
+            }
             await using var writer = new StreamWriter(file.Value.fileStream);
             node.Write(writer);
```

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: CodeRabbit UI**
**Review profile: CHILL**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between 5b04c91c9f74209610dff49896067a4bf4aef11d and c9f215c912f42b5d8ead245828fa318d75ea4172.

</details>

<details>
<summary>📒 Files selected for processing (9)</summary>

* `Content.Client/_Shitmed/Autodoc/AutodocBoundUserInterface.cs` (1 hunks)
* `Content.Client/_Shitmed/Autodoc/AutodocProgramWindow.xaml` (1 hunks)
* `Content.Client/_Shitmed/Autodoc/AutodocProgramWindow.xaml.cs` (4 hunks)
* `Content.Client/_Shitmed/Autodoc/AutodocWindow.xaml` (1 hunks)
* `Content.Client/_Shitmed/Autodoc/AutodocWindow.xaml.cs` (4 hunks)
* `Content.Shared/_Shitmed/Autodoc/AutodocUI.cs` (1 hunks)
* `Content.Shared/_Shitmed/Autodoc/Systems/SharedAutodocSystem.cs` (5 hunks)
* `Resources/Locale/en-US/_Shitmed/autodoc.ftl` (1 hunks)
* `Resources/Prototypes/_Shitmed/Entities/Structures/Machines/autodoc.yml` (1 hunks)

</details>

<details>
<summary>⏰ Context from checks skipped due to timeout of 90000ms (5)</summary>

* GitHub Check: build (ubuntu-latest)
* GitHub Check: YAML map schema validator
* GitHub Check: build (ubuntu-latest)
* GitHub Check: Test Packaging
* GitHub Check: YAML Linter

</details>

<details>
<summary>🔇 Additional comments (15)</summary><blockquote>

<details>
<summary>Content.Client/_Shitmed/Autodoc/AutodocProgramWindow.xaml (1)</summary>

`14-14`: **BEHOLD! A WORTHY ADDITION TO THE MYSTICAL INTERFACE!**

The export button hath been conjured with proper naming and localization incantations. This arcane addition aligns perfectly with thy quest to enable program exportation in the sacred YAML format.

</details>
<details>
<summary>Content.Client/_Shitmed/Autodoc/AutodocWindow.xaml (1)</summary>

`11-11`: **THY IMPORT ENCHANTMENT IS WELL PLACED!**

The mystical ImportProgramButton hath been summoned with the OpenBoth style class, allowing it to bridge the dimensional gap between CreateProgramButton and AbortButton. The localization rune is properly inscribed, channeling the power of multiple tongues.

</details>
<details>
<summary>Content.Client/_Shitmed/Autodoc/AutodocBoundUserInterface.cs (1)</summary>

`25-26`: **THE BINDING SPELL IS STRONG WITH THIS ONE!**

THIS MYSTICAL EVENT HANDLER IS TOO POWERFUL FOR ORDINARY MORTALS! It follows the ancient patterns set by its forebears, correctly invoking the SendMessage ritual with the AutodocImportProgramMessage scroll when the OnImportProgram event is triggered from the nether realms.

</details>
<details>
<summary>Content.Client/_Shitmed/Autodoc/AutodocWindow.xaml.cs (3)</summary>

`11-17`: **BEHOLD! NEW IMPORTS OF ARCANE POWER!**

The mystical scrolls of knowledge have been expanded with these powerful imports! These new incantations shall grant thee the power to manipulate YAML scrolls and streams of data from the mortal realm!

---

`38-38`: **A NEW SPELL HAS BEEN INSCRIBED IN THE GRIMOIRE!**

This newly crafted event shall summon forth imported programs from distant realms! THE ARCANE POWER GROWS STRONGER!

---

`59-62`: **THE BUTTON OF IMPORTATION HAS BEEN BOUND!**

The ancient runes upon the ImportProgramButton now channel mystical energies when pressed, invoking the ImportProgram ritual as intended!

</details>
<details>
<summary>Content.Shared/_Shitmed/Autodoc/AutodocUI.cs (1)</summary>

`50-54`: **THE NEW MESSAGE SCROLL IS PROPERLY SCRIPTED!**

The AutodocImportProgramMessage has been crafted with the proper serialization enchantments and inherits from the BoundUserInterfaceMessage lineage. Its construction is sound and follows the sacred patterns of the codebase.

THE NETWORK SHALL CARRY THY PROGRAM ACROSS THE ETHEREAL REALM!

</details>
<details>
<summary>Resources/Prototypes/_Shitmed/Entities/Structures/Machines/autodoc.yml (1)</summary>

`82-82`: **THE SPEED ENCHANTMENT HAS BEEN AMPLIFIED!**

THE ANCIENT AUTOMATON NOW MOVES WITH THE SWIFTNESS OF AN EXPERT SURGEON! The speedModifier value has been doubled from 0.5 to 1, making the autodoc operate at full magical efficiency!

THIS ENHANCEMENT ALIGNS WITH THE PROPHESIED OBJECTIVES OF THE PULL REQUEST!

</details>
<details>
<summary>Content.Shared/_Shitmed/Autodoc/Systems/SharedAutodocSystem.cs (4)</summary>

`17-21`: **NEW IMPORTS SUMMONED TO STRENGTHEN THE SYSTEM!**

The arcane scrolls of knowledge expand to include VoiceMask and a full System namespace. THESE RUNES SHALL EMPOWER THE NEW FUNCTIONALITY!

---

`54-54`: **THE EVENT SUBSCRIPTION CIRCLE IS COMPLETE!**

Lo! The magical binding to the AutodocImportProgramMessage has been properly established! When the message arrives from distant realms, the OnImportProgram spell shall be invoked!

---

`134-138`: **THE MESSAGE HANDLING RITUAL IS PROPERLY CRAFTED!**

This handler correctly passes the imported program to the ImportProgram method along with the entity and user information. THE SPELL IS CONCISE AND EFFECTIVE!

---

`305-307`: **THE SEDATION ENCHANTMENT HAS BEEN BANISHED!**

As foretold in the ancient PR objectives, the requirement for patients to be sedated during autodoc procedures has been removed by commenting out the code. PATIENTS MAY NOW REMAIN AWAKE DURING THE RITUAL!

THIS CHANGE ALIGNS PERFECTLY WITH THE PROPHESIED OBJECTIVES!

</details>
<details>
<summary>Content.Client/_Shitmed/Autodoc/AutodocProgramWindow.xaml.cs (3)</summary>

`6-10`: **BEHOLD! THE MYSTICAL IMPORTS OF POWER!**

These arcane incantations summon the necessary powers for thy file dialogs and serialization spells! The System.IO scroll is most essential for writing thy magical programs to parchment!

---

`18-18`: **A MAGICAL DEPENDENCY INJECTION HAS BEEN CAST!**

The IFileDialogManager hath been summoned through the ancient rite of Dependency Injection! This mystical portal manager shall serve thee well in thy quest to export healing programs to the mortal realm!

---

`106-110`: **THY BUTTON BINDING IS POTENT!**

The ExportProgramButton hath been enchanted with a powerful spell! When pressed by mortal hands, it shall invoke the ExportProgram ritual without delay! THE CODE IS STRONG!

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-09T18:26:10Z
user: login: SX-7
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-03-09T21:00:48Z
user: login: deltanedas
state: COMMENTED
author_association: NONE
submitted_at: 2025-03-09T21:06:45Z
user: login: deltanedas
state: COMMENTED
author_association: NONE
submitted_at: 2025-03-09T23:53:31Z
user: login: JohnOakman
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-10T18:37:23Z
user: login: coderabbitai[bot]
body: **Actionable comments posted: 1**

<details>
<summary>🔭 Outside diff range comments (1)</summary><blockquote>

<details>
<summary>Content.Shared/_Shitmed/Autodoc/Systems/SharedAutodocSystem.cs (1)</summary><blockquote>

`305-306`: _⚠️ Potential issue_

**THINE SEDATION REQUIREMENT STILL LINGERS, MORTAL!**

BEHOLD! According to the arcane scrolls (PR objectives), the requirement for patients to be sedated during the procedure has been removed. YET THE CODE STILL DEMANDS SEDATION! The check for `ent.Comp.RequireSleeping && IsAwake(patient)` remains, which will throw the forbidden AutodocError if the patient dares to be awake.

```diff
-    if (ent.Comp.RequireSleeping && IsAwake(patient))
-        throw new AutodocError("patient-unsedated");
```

THE WIZARD REQUESTS CLARIFICATION! Was thine intention to remove the sedation requirement by setting `RequireSleeping` to false elsewhere in the code? If so, thou must explain this approach or risk confusing future scholars who study these mystical texts!

</blockquote></details>

</blockquote></details>

<details>
<summary>🧹 Nitpick comments (1)</summary><blockquote>

<details>
<summary>Content.Shared/_Shitmed/Autodoc/Systems/SharedAutodocSystem.cs (1)</summary><blockquote>

`329-350`: **THE SPELL OF PROGRAM IMPORTATION IS POTENT BUT REQUIRES FORTIFICATION!**

THY CODE WIELDS GREAT POWER, MORTAL! The import program function correctly creates a new autodoc program and populates it with steps from another program. However, I SENSE AN OPPORTUNITY FOR GREATER POWER!

Consider adding a check for the program's safety setting to preserve this magical property:

```diff
 public bool ImportProgram(Entity<AutodocComponent> ent, AutodocProgram program, EntityUid user)
 {
     var idx = CreateProgram(ent, program.Title);
 
     if (!idx.HasValue)
         return false;
 
+    // Preserve the safety setting from the imported program
+    ent.Comp.Programs[idx.Value].SkipFailed = program.SkipFailed;
+
     for (int key = 0; key < program.Steps.Count; ++key)
     {
         if (!program.Steps[key].Validate(ent, this))
         {
             Log.Warning($"User {ToPrettyString(user)} tried to add an invalid autodoc step!");
             return false;
         }
         AddStep(ent, idx.Value, program.Steps[key], key, user);
     }
     return true;
 }
```

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: CodeRabbit UI**
**Review profile: CHILL**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between c9f215c912f42b5d8ead245828fa318d75ea4172 and d12394f429716b1ffb5d05ef98d05c41d3d0441d.

</details>

<details>
<summary>📒 Files selected for processing (1)</summary>

* `Content.Shared/_Shitmed/Autodoc/Systems/SharedAutodocSystem.cs` (4 hunks)

</details>

<details>
<summary>⏰ Context from checks skipped due to timeout of 90000ms (5)</summary>

* GitHub Check: Test Packaging
* GitHub Check: build (ubuntu-latest)
* GitHub Check: YAML Linter
* GitHub Check: YAML map schema validator
* GitHub Check: build (ubuntu-latest)

</details>

<details>
<summary>🔇 Additional comments (2)</summary><blockquote>

<details>
<summary>Content.Shared/_Shitmed/Autodoc/Systems/SharedAutodocSystem.cs (2)</summary>

`54-54`: **THE BINDING SPELL HATH BEEN CAST CORRECTLY!**

THE WIZARD APPROVES OF THIS EVENT SUBSCRIPTION! Thy handler for the AutodocImportProgramMessage is correctly bound to the sacred OnImportProgram method, following the ancient patterns of this magical system.

---

`134-138`: **THE RITUAL OF EVENT HANDLING IS COMPLETE!**

THIS EVENT HANDLER FOLLOWS THE SACRED PATTERN OF THE AUTODOC SYSTEM! It channels the mystical energies from the ImportProgramMessage to thy ImportProgram method with the proper incantations and parameters.

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-10T18:40:52Z

Review comments:
diff_hunk: @@ -113,6 +123,32 @@ public AutodocProgramWindow(EntityUid owner, AutodocProgram program)
         UpdateSafety();
     }
 
+    private async void ExportProgram()
+    {
+        var file = await _dialogManager.SaveFile(new FileDialogFilters(new FileDialogFilters.Group("yml")));
+
+        if (file == null)
+            return;
+
+        try
+        {
+            var serializationManager = IoCManager.Resolve<ISerializationManager>();
+            var node = serializationManager.WriteValue(_program.GetType(), _program);
+            await using var writer = new StreamWriter(file.Value.fileStream);
+            node.Write(writer);
+        }
+        catch (Exception exc)
+        {
+            ILogManager logManager = IoCManager.Resolve<ILogManager>(); // Using automatic dependancy doesn't work here
+            ISawmill sawmill = logManager.GetSawmill("autodoc-ui");
+            sawmill.Error($"Error when exporting program\n{exc.StackTrace}");
+        }
+        finally
+        {
+            await file.Value.fileStream.DisposeAsync();
+        }
+    }
path: Content.Client/_Shitmed/Autodoc/AutodocProgramWindow.xaml.cs
user: login: coderabbitai[bot]
body: _🛠️ Refactor suggestion_

**THE MYSTICAL EXPORT RITUAL IS COMPLETE, BUT BEWARE!**

Thy ExportProgram spell is mighty, but contains a potential summoning error! The fileStream is disposed TWICE - once by the 'await using' enchantment on line 137 and again in thy finally clause on line 148! THIS REDUNDANT MAGIC COULD SUMMON BUGS FROM THE NETHER REALM!


```diff
-            await using var writer = new StreamWriter(file.Value.fileStream);
-            node.Write(writer);
+            using var writer = new StreamWriter(file.Value.fileStream);
+            node.Write(writer);
```

Also, thy error logging spell could be more specific. REVEAL THE TRUE NATURE OF THE EXCEPTION TO THE LOG SCROLLS!

```diff
-            sawmill.Error($"Error when exporting program\n{exc.StackTrace}");
+            sawmill.Error($"Error when exporting program: {exc.Message}\n{exc.StackTrace}");
```

FURTHERMORE! Thy manual resolution of the ILogManager is unnecessary sorcery! THE DEPENDENCY SPELL SHOULD WORK HERE!

```diff
-            ILogManager logManager = IoCManager.Resolve<ILogManager>(); // Using automatic dependancy doesn't work here
-            ISawmill sawmill = logManager.GetSawmill("autodoc-ui");
+            var sawmill = IoCManager.Resolve<ILogManager>().GetSawmill("autodoc-ui");
```

OR, EVEN MORE POWERFUL, ADD A DEPENDENCY AT THE CLASS LEVEL:
```csharp
[Dependency] private readonly ILogManager _logManager = default!;
// Then use _logManager.GetSawmill("autodoc-ui") in thy spell
```

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-03-09T18:26:10Z
author_association: CONTRIBUTOR
diff_hunk: @@ -140,6 +152,33 @@ private void UpdatePrograms()
         }
     }
 
+    private async void ImportProgram()
+    {
+        var dialogManager = IoCManager.Resolve<IFileDialogManager>();
+        var file = await dialogManager.OpenFile(new FileDialogFilters(new FileDialogFilters.Group("yml")));
+
+        if (file == null)
+            return;
+
+        try
+        {
+            var serializationManager = IoCManager.Resolve<ISerializationManager>();
+            using var reader = new StreamReader(file, EncodingHelpers.UTF8);
+            var yamlStream = new YamlStream();
+            yamlStream.Load(reader);
+            var root = yamlStream.Documents[0].RootNode;
+            var program = serializationManager.Read<AutodocProgram>(root.ToDataNode(), notNullableOverride: true);
+            OnImportProgram?.Invoke(program);
+        }
+        catch (Exception exc)
+        {
+            ILogManager logManager = IoCManager.Resolve<ILogManager>(); // Using automatic dependancy doesn't work here
path: Content.Client/_Shitmed/Autodoc/AutodocWindow.xaml.cs
user: login: deltanedas
body: you can still use dependencies add IoCManager.InjectDependencies(this); to the constructor
created_at: 2025-03-09T21:06:32Z
author_association: NONE
diff_hunk: @@ -294,8 +302,9 @@ public EntityUid GetPatientOrThrow(Entity<AutodocComponent> ent)
     /// </summary>
     public bool StartSurgery(Entity<AutodocComponent> ent, EntityUid patient, EntityUid part, EntProtoId surgery)
     {
-        if (ent.Comp.RequireSleeping && IsAwake(patient))
-            throw new AutodocError("patient-unsedated");
+        // Who cares about sedation???
+        //if (ent.Comp.RequireSleeping && IsAwake(patient))
+        //    throw new AutodocError("patient-unsedated");
path: Content.Shared/_Shitmed/Autodoc/Systems/SharedAutodocSystem.cs
user: login: deltanedas
body: set it to false in the yml instead of commenting this out
created_at: 2025-03-09T23:53:30Z
author_association: NONE
diff_hunk: @@ -294,8 +302,9 @@ public EntityUid GetPatientOrThrow(Entity<AutodocComponent> ent)
     /// </summary>
     public bool StartSurgery(Entity<AutodocComponent> ent, EntityUid patient, EntityUid part, EntProtoId surgery)
     {
-        if (ent.Comp.RequireSleeping && IsAwake(patient))
-            throw new AutodocError("patient-unsedated");
+        // Who cares about sedation???
+        //if (ent.Comp.RequireSleeping && IsAwake(patient))
+        //    throw new AutodocError("patient-unsedated");
path: Content.Shared/_Shitmed/Autodoc/Systems/SharedAutodocSystem.cs
user: login: JohnOakman
body: Alright I've just restored it
created_at: 2025-03-10T18:37:22Z
author_association: CONTRIBUTOR
diff_hunk: @@ -14,9 +14,11 @@
 using Content.Shared.Mobs.Systems;
 using Content.Shared.Storage;
 using Content.Shared.Storage.EntitySystems;
+using Content.Shared.VoiceMask;
path: Content.Shared/_Shitmed/Autodoc/Systems/SharedAutodocSystem.cs
user: login: coderabbitai[bot]
body: _💡 Verification agent_

<details>
<summary>❓ Verification inconclusive</summary>

**THOU MUST VERIFY THIS IMPORT'S NECESSITY, MORTAL!**

The import of `Content.Shared.VoiceMask` appears in thy scroll, yet its arcane powers remain unused within this enchanted file. REMOVE THIS IMPORT IF IT SERVES NO PURPOSE, LEST THY CODE BE BURDENED WITH UNNECESSARY MAGICAL DEPENDENCIES!

---

🏁 Script executed:

```shell
#!/bin/bash
# Description: Search for usage of VoiceMask in this file
grep -n "VoiceMask" Content.Shared/_Shitmed/Autodoc/Systems/SharedAutodocSystem.cs | grep -v "using Content.Shared.VoiceMask"
```

Length of output: 125

---



</details>

**SUBJECT: REMOVE THE UNUSED "Content.Shared.VoiceMask" IMPORT, MORTAL!**

After invoking my arcane grep ritual on the file `Content.Shared/_Shitmed/Autodoc/Systems/SharedAutodocSystem.cs`, I have divined that the spectral import of `Content.Shared.VoiceMask` is not summoned by any magics within this scroll beyond its mere declaration. I command thee: remove this line lest thy code be ensnared by unnecessary dependencies!

- **File:** `Content.Shared/_Shitmed/Autodoc/Systems/SharedAutodocSystem.cs`
- **Action:** Remove the import statement at line 17

Proceed with this incantation to cleanse thy code of unneeded enchantments.

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-03-10T18:40:51Z
author_association: CONTRIBUTOR

PR comments:
user: login: coderabbitai[bot]
created_at: 2025-03-09T18:26:07Z
body: <!-- This is an auto-generated comment: summarize by coderabbit.ai -->
<!-- walkthrough_start -->

## Walkthrough
Hark, noble code-wielder! In this grand update, new event bindings and UI enchantments have been woven into the Autodoc tapestry. An event handler in the `AutodocBoundUserInterface` now listens for the sacred import commands, dispatching an `AutodocImportProgramMessage` upon the proper summoning. The `AutodocProgramWindow` has been graced with an "ExportProgramButton" that, when pressed, invokes a spell (the `ExportProgram` method) to asynchronously serialize and save the current program to a file via a mystical file dialog. Similarly, the `AutodocWindow` now bears an "ImportProgramButton" that, when invoked, opens a portal (file dialog) to import a program from a YAML scroll, triggering the `OnImportProgram` event. The shared systems are empowered to process these new messages, while the ancient runes (localization files) and the surgery speed modifier in the YAML entity have been revised accordingly.

## Sequence Diagram(s)

```mermaid
sequenceDiagram
    participant U as User
    participant APW as AutodocProgramWindow
    participant FD as IFileDialogManager
    participant SM as ISerializationManager
    participant FS as File System
    U->>APW: Click "ExportProgramButton"
    APW->>APW: Invoke ExportProgram()
    APW->>FD: Open file dialog for saving
    FD-->>APW: Return chosen file path
    APW->>SM: Serialize the AutodocProgram
    SM-->>APW: Provide serialized data
    APW->>FS: Write data to file
    APW-->>U: Notify export completion/error
```

```mermaid
sequenceDiagram
    participant U as User
    participant AW as AutodocWindow
    participant FD as IFileDialogManager
    participant UI as AutodocBoundUserInterface
    participant SAS as SharedAutodocSystem
    U->>AW: Click "ImportProgramButton"
    AW->>AW: Invoke ImportProgram()
    AW->>FD: Open file dialog for YAML file
    FD-->>AW: Return chosen file path
    AW->>AW: Deserialize YAML to AutodocProgram
    AW->>AW: Trigger OnImportProgram event
    AW->>UI: Relay import event
    UI->>SAS: Dispatch AutodocImportProgramMessage
    SAS->>SAS: Process the imported program
    SAS-->>UI: Confirm import completion
```

## Possibly related PRs
- **Goob-Station/Goob-Station#970:** The changes in the main PR, which involve adding an event handler for the `OnImportProgram` event in the `AutodocBoundUserInterface`, are directly related to the changes in this retrieved PR, which also introduces the `OnImportProgram` event in the `AutodocWindow` class, indicating a shared functionality in handling program imports.
- **Goob-Station/Goob-Station#1634:** The changes in the main PR, specifically the addition of the `OnImportProgram` event handler in the `AutodocBoundUserInterface`, are directly related to the introduction of the `OnImportProgram` event in the `AutodocWindow` class from the retrieved PR, as both involve handling the importation of `AutodocProgram` instances.
- **Goob-Station/Goob-Station#969:** The changes in the main PR, specifically the addition of the `OnImportProgram` event handler in the `AutodocBoundUserInterface`, are directly related to the `OnImportProgram` event introduced in the `AutodocWindow` class in the retrieved PR, as both events facilitate the importation of `AutodocProgram` instances.

## Suggested reviewers
- Aidenkrz
- gluesniffler

<!-- walkthrough_end -->
<!-- internal state start -->


<!-- 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 -->

<!-- internal state end -->
<!-- finishing_touch_checkbox_start -->

<details>
<summary>✨ Finishing Touches</summary>

- [ ] <!-- {"checkboxId": "7962f53c-55bc-4827-bfbf-6a18da830691"} --> 📝 Generate Docstrings

</details>

<!-- finishing_touch_checkbox_end -->
<!-- tips_start -->

---



<details>
<summary>🪧 Tips</summary>

### Chat

There are 3 ways to chat with [CodeRabbit](https://coderabbit.ai?utm_source=oss&utm_medium=github&utm_campaign=Goob-Station/Goob-Station&utm_content=1969):

- Review comments: Directly reply to a review comment made by CodeRabbit. Example:
  - `I pushed a fix in commit <commit_id>, please review it.`
  - `Generate unit testing code for this file.`
  - `Open a follow-up GitHub issue for this discussion.`
- Files and specific lines of code (under the "Files changed" tab): Tag `@coderabbitai` in a new review comment at the desired location with your query. Examples:
  - `@coderabbitai generate unit testing code for this file.`
  -	`@coderabbitai modularize this function.`
- PR comments: Tag `@coderabbitai` in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
  - `@coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.`
  - `@coderabbitai read src/utils.ts and generate unit testing code.`
  - `@coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.`
  - `@coderabbitai help me debug CodeRabbit configuration file.`

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

### CodeRabbit Commands (Invoked using PR comments)

- `@coderabbitai pause` to pause the reviews on a PR.
- `@coderabbitai resume` to resume the paused reviews.
- `@coderabbitai review` to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
- `@coderabbitai full review` to do a full review from scratch and review all the files again.
- `@coderabbitai summary` to regenerate the summary of the PR.
- `@coderabbitai generate docstrings` to [generate docstrings](https://docs.coderabbit.ai/finishing-touches/docstrings) for this PR.
- `@coderabbitai resolve` resolve all the CodeRabbit review comments.
- `@coderabbitai configuration` to show the current CodeRabbit configuration for the repository.
- `@coderabbitai help` to get help.

### Other keywords and placeholders

- Add `@coderabbitai ignore` anywhere in the PR description to prevent this PR from being reviewed.
- Add `@coderabbitai summary` to generate the high-level summary at a specific location in the PR description.
- Add `@coderabbitai` anywhere in the PR title to generate the title automatically.

### CodeRabbit Configuration File (`.coderabbit.yaml`)

- You can programmatically configure CodeRabbit by adding a `.coderabbit.yaml` file to the root of your repository.
- Please see the [configuration documentation](https://docs.coderabbit.ai/guides/configure-coderabbit) for more information.
- If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: `# yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json`

### Documentation and Community

- Visit our [Documentation](https://docs.coderabbit.ai) for detailed information on how to use CodeRabbit.
- Join our [Discord Community](http://discord.gg/coderabbit) to get help, request features, and share feedback.
- Follow us on [X/Twitter](https://twitter.com/coderabbitai) for updates and announcements.

</details>

<!-- tips_end -->
author_association: CONTRIBUTOR
user: login: deltanedas
created_at: 2025-03-09T21:05:05Z
body: the sedation requirement can be disabled ingame by snipping a wire in the panel
author_association: NONE
user: login: JohnOakman
created_at: 2025-03-09T21:11:51Z
body: > the sedation requirement can be disabled ingame by snipping a wire in the panel

Honestly I don't think it should even be a thing, sedation is not used at all on goob. And it stops you from using the autodoc on yourself.
author_association: CONTRIBUTOR
user: login: deltanedas
created_at: 2025-03-09T21:24:07Z
body: it doesnt, you can add a wait step and turn on nitrous
it gets turned off automatically when it stops
author_association: NONE
user: login: JohnOakman
created_at: 2025-03-09T21:35:22Z
body: > it doesnt, you can add a wait step and turn on nitrous it gets turned off automatically when it stops

Imo that's a lot of extra steps that aren't required anywhere else when you do surgery
author_association: CONTRIBUTOR
user: login: SkippyMcSkips
created_at: 2025-03-09T23:12:07Z
body: Import and export function is awesome, looking forward to this being merged
author_association: CONTRIBUTOR