Skip to main content

**字段级合并策略配置**:

  1. **字段级合并策略配置**:
    
    # config.yaml
    git:
      merge_strategy:
        # 默认策略:取 modified 最新的一方 (Last Write Wins)
        default: "take_latest_modified"
        
        # 特殊字段策略
        fields:
          # 标签:取并集。A端加了[urgent], B端加了[work] -> [urgent, work]
          tags: "union"
          
          # 关联:取并集
          related: "union"
          
          # 状态:取最新修改方(或自定义优先级:done > in-progress > todo)
          status: "take_latest_modified"
          
          # 正文内容:标准 Git 文本合并
          content: "git_standard"
    


    **冲突安全网**:
    - 当 Merge Driver 无法自动解决(如正文同一行被修改):
    - 生成 .conflict 备份文件
    - TUI 启动时检测到冲突文件 → 进入"冲突解决向导"模式
    - 用户可选择:保留本地 / 保留远程 / 手动合并

    ### 7.6 并发编辑检测

    **场景**:TUI 打开时,用户用 VS Code 编辑同一文件。

    **方案 1:Hash 检测**(基础)
    1. syn edit 前:记录文件 hash
    2. 外部编辑器关闭后:检查 hash
    3. 如果 hash 变化且非当前进程修改:
    - 提示冲突
    - 显示 diff
    - 用户选择:覆盖 / 合并 / 放弃

    **方案 2:watchdog 实时监听**(增强)

    使用 watchdog 库监听文件系统事件,TUI 运行时实时感知外部修改:

    
    # 伪代码示意 - 注意线程安全!
    from watchdog.observers import Observer
    from watchdog.events import FileSystemEventHandler
    
    class SynapseFileWatcher(FileSystemEventHandler):
        def __init__(self, app):
            self.app = app  # Textual App 实例
        
        def on_modified(self, event):
            if event.src_path == self.app.current_open_file:
                # ⚠️ 关键:watchdog 在独立线程,必须用 call_from_thread
                self.app.call_from_thread(
                    self.app.show_file_modified_warning,
                    event.src_path
                )
    


    **线程安全注意**:
    - watchdog 运行在独立线程
    - Textual UI 更新必须在主线程
    - 必须使用 app.call_from_thread() 跨线程调用,否则 TUI 会崩溃

    **自激抑制(Suspension Context)**:

    ⚠️ **问题**:当 Synapse 执行 git pullgit checkout 时,文件系统变化会触发 watchdog,错误地向用户报告"外部修改警告"。

    
    class FileWatcher:
        def __init__(self):
            self._suspended = False
        
        def pause(self):
            """暂停监听(Git 操作前调用)"""
            self._suspended = True
        
        def resume(self):
            """恢复监听(Git 操作后调用)"""
            self._suspended = False
        
        def on_modified(self, event):
            if self._suspended:
                return  # 忽略自身触发的变更
            # ... 正常处理外部修改
    
    class SynapseApp:
        def perform_git_operation(self):
            self.file_watcher.pause()  # 暂停监听
            try:
                git.pull()
                self.index.refresh()   # 主动刷新索引
            finally:
                self.file_watcher.resume()  # 恢复监听
    


    **行为**:
    - TUI 打开时,自动启动 watchdog **仅监听当前 Namespace 目录**(性能优化)
    - 切换 Namespace 时,watchdog 同步切换监听目标
    - 检测到当前查看/编辑的文件被外部修改 → 实时弹出警告
    - 用户可选择:重新加载 / 忽略 / 查看 diff
    - **Git 操作期间自动暂停监听**,避免误报

    
    class SynapseFileWatcher:
        def __init__(self, app):
            self.app = app
            self.observer = Observer()
            self._current_path = None
            self._started = False
        
        def start(self):
            """启动观察者线程(TUI 启动时调用)"""
            if not self._started:
                self.observer.start()
                self._started = True
        
        def stop(self):
            """停止观察者线程(TUI 退出时调用)"""
            if self._started:
                self.observer.stop()
                self.observer.join()  # 等待线程结束
                self._started = False
        
        def watch_namespace(self, namespace_path: str):
            """切换监听目标到指定 Namespace"""
            # 1. 取消之前的监听
            if self._current_path:
                self.observer.unschedule_all()
            
            # 2. 调度新的监听目标
            self._current_path = namespace_path
            self.observer.schedule(
                self, 
                namespace_path, 
                recursive=True  # 监听 Namespace 下所有子目录
            )
            
            # 3. 确保观察者已启动
            self.start()
    


    ---

    ## 8. 媒体管理(优先级低,预留设计)

    > ⚠️ 本章节为预留设计,待后续迭代完善。初始版本仅支持外部链接引用媒体。

    ### 8.1 设计原则

    - **仓库纯净**:禁止二进制文件直接入库
    - **体验友好**:提供便捷的截图/贴图工作流

    ### 8.2 syn media 命令

    
    # 从剪贴板上传图片
    syn media paste
    # → 自动上传到配置的存储 → 返回 Markdown 链接
    
    # 上传指定文件
    syn media upload /path/to/image.png
    
    # 输出示例
    ![image](https://your-bucket.s3.amazonaws.com/synapse/10.001.0015/abc123.png)
    


    ### 8.3 媒体存储目录结构

    **建议使用资源 ID 作为目录名**,便于关联和管理:

    
    媒体存储根目录/
    ├── 10.001.0015/                # 资源 ID 为目录名
    │   ├── screenshot-001.png
    │   └── diagram.svg
    ├── 10.002.0003/
    │   └── meeting-photo.jpg
    └── _orphan/                    # 未关联资源的媒体(待清理)
        └── temp-upload.png
    


    ### 8.4 媒体存储配置