@atlashub/smartstack-cli 4.23.0 → 4.24.0

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 (27) hide show
  1. package/package.json +1 -1
  2. package/templates/skills/ba-generate-html/html/ba-interactive.html +950 -1055
  3. package/templates/skills/ba-generate-html/html/src/scripts/01-data-init.js +1 -2
  4. package/templates/skills/ba-generate-html/html/src/scripts/02-navigation.js +0 -1
  5. package/templates/skills/ba-generate-html/html/src/scripts/03-render-cadrage.js +0 -39
  6. package/templates/skills/ba-generate-html/html/src/scripts/05-render-specs.js +0 -1
  7. package/templates/skills/ba-generate-html/html/src/scripts/07-render-handoff.js +0 -1
  8. package/templates/skills/ba-generate-html/html/src/scripts/08-editing.js +133 -135
  9. package/templates/skills/ba-generate-html/html/src/scripts/10-comments.js +199 -199
  10. package/templates/skills/ba-generate-html/html/src/scripts/11-review-panel.js +165 -166
  11. package/templates/skills/ba-generate-html/html/src/styles/05-modules.css +444 -454
  12. package/templates/skills/ba-generate-html/html/src/template.html +0 -49
  13. package/templates/skills/ba-generate-html/references/data-build.md +176 -182
  14. package/templates/skills/ba-generate-html/references/data-mapping.md +295 -301
  15. package/templates/skills/ba-generate-html/steps/step-01-collect.md +1 -1
  16. package/templates/skills/ba-generate-html/steps/step-02-build-data.md +0 -9
  17. package/templates/skills/derive-prd/SKILL.md +9 -9
  18. package/templates/skills/derive-prd/references/acceptance-criteria.md +166 -116
  19. package/templates/skills/derive-prd/references/entity-domain-mapping.md +5 -5
  20. package/templates/skills/derive-prd/references/handoff-file-templates.md +12 -12
  21. package/templates/skills/derive-prd/references/handoff-mappings.md +13 -14
  22. package/templates/skills/derive-prd/references/handoff-seeddata-generation.md +1 -1
  23. package/templates/skills/derive-prd/references/readiness-scoring.md +41 -50
  24. package/templates/skills/derive-prd/schemas/handoff-schema.json +2 -2
  25. package/templates/skills/derive-prd/steps/step-00-validate.md +73 -52
  26. package/templates/skills/derive-prd/steps/step-01-transform.md +86 -43
  27. package/templates/skills/ba-generate-html/html/src/partials/cadrage-risks.html +0 -48
@@ -473,460 +473,450 @@ body {
473
473
 
474
474
 
475
475
  /* --- 05-modules.css --- */
476
- /* ============================================
477
- USE CASE LIST
478
- ============================================ */
479
- .uc-list { list-style: none; }
480
-
481
- .uc-item {
482
- background: var(--bg-card);
483
- border: 1px solid var(--border);
484
- border-radius: 8px;
485
- padding: 1rem 1.25rem;
486
- margin-bottom: 0.75rem;
487
- transition: border-color var(--transition-fast);
488
- }
489
- .uc-item:hover { border-color: var(--border-light); }
490
-
491
- .uc-header {
492
- display: flex;
493
- align-items: center;
494
- gap: 0.75rem;
495
- margin-bottom: 0.5rem;
496
- }
497
- .uc-id {
498
- font-size: 0.7rem;
499
- font-weight: 700;
500
- color: var(--primary-light);
501
- background: rgba(99,102,241,0.1);
502
- padding: 0.15rem 0.5rem;
503
- border-radius: 4px;
504
- }
505
- .uc-title { font-weight: 600; color: var(--text-bright); flex: 1; }
506
- .uc-actions { display: flex; gap: 0.3rem; opacity: 0; transition: opacity var(--transition-fast); }
507
- .uc-item:hover .uc-actions { opacity: 1; }
508
-
509
- .uc-detail { font-size: 0.875rem; color: var(--text); }
510
- .uc-detail-label { color: var(--text-muted); font-size: 0.75rem; font-weight: 600; margin-top: 0.5rem; }
511
-
512
- .uc-actors {
513
- display: flex; gap: 0.4rem; margin-top: 0.3rem; flex-wrap: wrap;
514
- }
515
- .uc-actor {
516
- font-size: 0.7rem;
517
- padding: 0.1rem 0.4rem;
518
- background: rgba(6,182,212,0.1);
519
- color: var(--accent);
520
- border-radius: 4px;
521
- border: 1px solid rgba(6,182,212,0.2);
522
- }
523
-
524
- /* ============================================
525
- BUSINESS RULE LIST
526
- ============================================ */
527
- .br-item {
528
- display: flex;
529
- align-items: flex-start;
530
- gap: 0.75rem;
531
- padding: 0.75rem 1rem;
532
- background: var(--bg-card);
533
- border: 1px solid var(--border);
534
- border-radius: 8px;
535
- margin-bottom: 0.5rem;
536
- }
537
- .br-category {
538
- font-size: 0.65rem;
539
- font-weight: 700;
540
- text-transform: uppercase;
541
- padding: 0.15rem 0.4rem;
542
- border-radius: 4px;
543
- flex-shrink: 0;
544
- min-width: 70px;
545
- text-align: center;
546
- }
547
- .br-cat-validation { background: rgba(99,102,241,0.15); color: var(--primary-light); }
548
- .br-cat-calculation { background: rgba(234,179,8,0.15); color: #facc15; }
549
- .br-cat-workflow { background: rgba(249,115,22,0.15); color: var(--secondary); }
550
- .br-cat-security { background: rgba(239,68,68,0.15); color: #f87171; }
551
- .br-cat-data { background: rgba(6,182,212,0.15); color: var(--accent); }
552
- .br-text { flex: 1; font-size: 0.875rem; }
553
-
554
- /* ============================================
555
- STAKEHOLDER TABLE
556
- ============================================ */
557
- .stakeholder-grid {
558
- display: grid;
559
- grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
560
- gap: 1rem;
561
- }
562
- .stakeholder-card {
563
- background: var(--bg-card);
564
- border: 1px solid var(--border);
565
- border-radius: 10px;
566
- padding: 1rem;
567
- }
568
- .stakeholder-card:hover { border-color: var(--border-light); }
569
- .stakeholder-role { font-weight: 600; color: var(--text-bright); margin-bottom: 0.25rem; }
570
- .stakeholder-function { font-size: 0.8rem; color: var(--text-muted); margin-bottom: 0.5rem; }
571
- .stakeholder-tasks { list-style: none; }
572
- .stakeholder-tasks li {
573
- font-size: 0.8rem;
574
- padding: 0.15rem 0;
575
- color: var(--text);
576
- }
577
- .stakeholder-tasks li::before { content: "- "; color: var(--primary-light); }
578
- .stakeholder-meta {
579
- display: flex; gap: 0.75rem; margin-top: 0.5rem; padding-top: 0.5rem;
580
- border-top: 1px solid var(--border);
581
- }
582
- .stakeholder-meta span { font-size: 0.7rem; color: var(--text-muted); }
583
-
584
- /* ============================================
585
- PROCESS FLOW
586
- ============================================ */
587
- .process-flow {
588
- display: flex;
589
- align-items: center;
590
- gap: 0.25rem;
591
- overflow-x: auto;
592
- padding: 1rem 0;
593
- }
594
- .process-step {
595
- background: var(--bg-card);
596
- border: 1px solid var(--border);
597
- border-radius: 8px;
598
- padding: 0.75rem 1rem;
599
- min-width: 140px;
600
- text-align: center;
601
- flex-shrink: 0;
602
- }
603
- .process-step:hover { border-color: var(--primary); }
604
- .process-step-number {
605
- font-size: 0.65rem;
606
- color: var(--primary-light);
607
- font-weight: 700;
608
- }
609
- .process-step-label { font-size: 0.8rem; color: var(--text-bright); font-weight: 500; }
610
- .process-arrow { color: var(--text-muted); font-size: 1.2rem; flex-shrink: 0; }
611
-
612
- /* ============================================
613
- RISK TABLE
614
- ============================================ */
615
- .risk-item {
616
- display: grid;
617
- grid-template-columns: auto 1fr auto auto;
618
- gap: 1rem;
619
- align-items: center;
620
- padding: 0.75rem 1rem;
621
- background: var(--bg-card);
622
- border: 1px solid var(--border);
623
- border-radius: 8px;
624
- margin-bottom: 0.5rem;
625
- }
626
- .risk-level {
627
- width: 10px; height: 10px;
628
- border-radius: 50%;
629
- }
630
- .risk-critical { background: var(--error); }
631
- .risk-medium { background: var(--warning); }
632
- .risk-low { background: var(--success); }
633
- .risk-text { font-size: 0.875rem; }
634
- .risk-probability, .risk-impact {
635
- font-size: 0.7rem;
636
- color: var(--text-muted);
637
- text-align: center;
638
- }
639
-
640
- /* ============================================
641
- MODULE CARDS (Decomposition)
642
- ============================================ */
643
- .module-grid {
644
- display: grid;
645
- grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
646
- gap: 1rem;
647
- }
648
- .module-card {
649
- background: var(--bg-card);
650
- border: 1px solid var(--border);
651
- border-radius: 10px;
652
- padding: 1rem;
653
- cursor: pointer;
654
- transition: all var(--transition-fast);
655
- position: relative;
656
- }
657
- .module-card:hover { border-color: var(--primary); }
658
- .module-card.selected { border-color: var(--primary); box-shadow: 0 0 0 2px rgba(99,102,241,0.2); }
659
- .module-card-header { display: flex; justify-content: space-between; align-items: start; margin-bottom: 0.5rem; }
660
- .module-card-code { font-weight: 700; color: var(--text-bright); font-size: 1rem; }
661
- .module-card-type {
662
- font-size: 0.65rem; text-transform: uppercase; letter-spacing: 0.05em;
663
- padding: 0.15rem 0.5rem; border-radius: 4px;
664
- background: rgba(99,102,241,0.1); color: var(--primary-light);
665
- }
666
- .module-card-desc { font-size: 0.8rem; color: var(--text-muted); margin-bottom: 0.5rem; }
667
- .module-card-meta { display: flex; gap: 0.75rem; font-size: 0.7rem; color: var(--text-muted); }
668
- .module-card-meta span { display: flex; align-items: center; gap: 0.2rem; }
669
- .module-card-remove {
670
- position: absolute; top: 0.5rem; right: 0.5rem;
671
- background: none; border: none; color: var(--text-muted);
672
- cursor: pointer; font-size: 0.8rem; opacity: 0;
673
- transition: opacity var(--transition-fast);
674
- }
675
- .module-card:hover .module-card-remove { opacity: 1; }
676
- .module-card-remove:hover { color: var(--error); }
677
-
678
- /* ============================================
679
- TABS (Module Specification)
680
- ============================================ */
681
- .tab-bar {
682
- display: flex;
683
- gap: 0;
684
- border-bottom: 1px solid var(--border);
685
- margin-bottom: 1.5rem;
686
- overflow-x: auto;
687
- }
688
- .tab-btn {
689
- padding: 0.6rem 1rem;
690
- background: none;
691
- border: none;
692
- border-bottom: 2px solid transparent;
693
- color: var(--text-muted);
694
- font-size: 0.8rem;
695
- font-family: inherit;
696
- cursor: pointer;
697
- white-space: nowrap;
698
- transition: all var(--transition-fast);
699
- }
700
- .tab-btn:hover { color: var(--text-bright); background: var(--bg-hover); }
701
- .tab-btn.active { color: var(--primary-light); border-bottom-color: var(--primary); font-weight: 500; }
702
- .tab-panel { display: none; }
703
- .tab-panel.active { display: block; }
704
-
705
- /* ============================================
706
- ENTITY TABLE
707
- ============================================ */
708
- .entity-block {
709
- background: var(--bg-card);
710
- border: 1px solid var(--border);
711
- border-radius: 10px;
712
- margin-bottom: 1rem;
713
- overflow: hidden;
714
- }
715
- .entity-header {
716
- display: flex; align-items: center; justify-content: space-between;
717
- padding: 0.75rem 1rem;
718
- background: var(--bg-hover);
719
- border-bottom: 1px solid var(--border);
720
- }
721
- .entity-name { font-weight: 600; color: var(--text-bright); }
722
- .entity-desc { font-size: 0.8rem; color: var(--text-muted); }
723
- .attr-table { width: 100%; border-collapse: collapse; }
724
- .attr-table th {
725
- text-align: left; font-size: 0.7rem; text-transform: uppercase;
726
- letter-spacing: 0.05em; color: var(--text-muted); padding: 0.5rem 0.75rem;
727
- border-bottom: 1px solid var(--border); font-weight: 600;
728
- }
729
- .attr-table td {
730
- padding: 0.5rem 0.75rem; font-size: 0.85rem;
731
- border-bottom: 1px solid rgba(51,65,85,0.3);
732
- }
733
- .attr-required { color: var(--error); font-weight: 700; }
734
-
735
- /* ============================================
736
- DEPENDENCY VISUALIZATION
737
- ============================================ */
738
- .dep-graph {
739
- display: flex; flex-direction: column; gap: 1.5rem;
740
- padding: 1.5rem; background: var(--bg-card); border: 1px solid var(--border);
741
- border-radius: 10px;
742
- }
743
- .dep-layer {
744
- display: flex; align-items: center; gap: 1rem;
745
- }
746
- .dep-layer-label {
747
- font-size: 0.7rem; color: var(--text-muted); text-transform: uppercase;
748
- letter-spacing: 0.05em; min-width: 80px; font-weight: 600;
749
- }
750
- .dep-layer-modules { display: flex; gap: 0.75rem; flex-wrap: wrap; }
751
- .dep-module {
752
- padding: 0.4rem 0.8rem; border-radius: 6px;
753
- background: rgba(99,102,241,0.1); border: 1px solid rgba(99,102,241,0.3);
754
- color: var(--primary-light); font-size: 0.8rem; font-weight: 500;
755
- }
756
- .dep-arrow {
757
- text-align: center; color: var(--text-muted); font-size: 1.2rem;
758
- padding-left: 80px;
759
- }
760
-
761
- /* ============================================
762
- STAT CARDS (Handoff)
763
- ============================================ */
764
- .stat-grid {
765
- display: grid; grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
766
- gap: 1rem; margin-bottom: 1.5rem;
767
- }
768
- .stat-card {
769
- background: var(--bg-card); border: 1px solid var(--border);
770
- border-radius: 10px; padding: 1rem; text-align: center;
771
- }
772
- .stat-value { font-size: 1.8rem; font-weight: 700; color: var(--text-bright); }
773
- .stat-label { font-size: 0.75rem; color: var(--text-muted); margin-top: 0.2rem; }
774
-
775
- /* ============================================
776
- CONSOLIDATION
777
- ============================================ */
778
- .interaction-item {
779
- display: flex; align-items: center; gap: 0.75rem;
780
- padding: 0.75rem 1rem; background: var(--bg-card);
781
- border: 1px solid var(--border); border-radius: 8px;
782
- margin-bottom: 0.5rem; font-size: 0.85rem;
783
- }
784
- .interaction-arrow { color: var(--primary-light); font-weight: 700; font-size: 1.1rem; }
785
- .interaction-type {
786
- font-size: 0.65rem; text-transform: uppercase; letter-spacing: 0.05em;
787
- padding: 0.15rem 0.4rem; border-radius: 4px;
788
- background: rgba(6,182,212,0.1); color: var(--accent);
789
- }
790
- .e2e-flow {
791
- display: flex; align-items: center; gap: 0.3rem;
792
- overflow-x: auto; padding: 1rem 0;
793
- }
794
- .e2e-step {
795
- padding: 0.5rem 0.75rem; border-radius: 6px; text-align: center;
796
- font-size: 0.75rem; flex-shrink: 0;
797
- background: var(--bg-card); border: 1px solid var(--border);
798
- }
799
- .e2e-step-module { font-weight: 600; color: var(--primary-light); font-size: 0.65rem; }
800
- .e2e-step-action { color: var(--text-bright); }
801
-
802
- /* ============================================
803
- DATA MODEL (Consolidation)
804
- ============================================ */
805
- .dm-summary {
806
- display: flex; gap: 1.5rem; margin-bottom: 1.5rem;
807
- padding: 1rem 1.5rem; background: var(--bg-card);
808
- border: 1px solid var(--border); border-radius: 10px;
809
- }
810
- .dm-summary-item { display: flex; align-items: baseline; gap: 0.4rem; }
811
- .dm-summary-value { font-size: 1.4rem; font-weight: 700; color: var(--text-bright); }
812
- .dm-summary-label { font-size: 0.8rem; color: var(--text-muted); }
813
- .dm-module-group { margin-bottom: 1.5rem; }
814
- .dm-module-header {
815
- display: flex; align-items: center; justify-content: space-between;
816
- padding: 0.5rem 0; margin-bottom: 0.75rem;
817
- border-bottom: 2px solid var(--primary);
818
- }
819
- .dm-module-name { font-weight: 700; color: var(--primary-light); font-size: 1rem; }
820
- .dm-module-count { font-size: 0.75rem; color: var(--text-muted); }
821
- .dm-entity-grid {
822
- display: grid; grid-template-columns: repeat(auto-fill, minmax(340px, 1fr)); gap: 1rem;
823
- }
824
- .dm-entity-card {
825
- background: var(--bg-card); border: 1px solid var(--border);
826
- border-radius: 10px; overflow: hidden;
827
- transition: border-color var(--transition-fast);
828
- }
829
- .dm-entity-card:hover { border-color: var(--border-light); }
830
- .dm-entity-header {
831
- display: flex; align-items: center; justify-content: space-between;
832
- padding: 0.6rem 0.75rem; background: var(--bg-hover);
833
- border-bottom: 1px solid var(--border);
834
- }
835
- .dm-entity-name { font-weight: 600; color: var(--text-bright); font-size: 0.95rem; }
836
- .dm-entity-attr-count {
837
- font-size: 0.65rem; color: var(--text-muted);
838
- background: rgba(99,102,241,0.1); padding: 0.1rem 0.4rem; border-radius: 4px;
839
- }
840
- .dm-entity-desc {
841
- padding: 0.5rem 0.75rem; font-size: 0.8rem; color: var(--text-muted);
842
- border-bottom: 1px solid var(--border);
843
- }
844
- .dm-attr-table { width: 100%; border-collapse: collapse; }
845
- .dm-attr-table th {
846
- text-align: left; font-size: 0.65rem; text-transform: uppercase;
847
- letter-spacing: 0.05em; color: var(--text-muted); padding: 0.4rem 0.75rem;
848
- border-bottom: 1px solid var(--border); font-weight: 600;
849
- }
850
- .dm-attr-table td { padding: 0.35rem 0.75rem; font-size: 0.8rem; border-bottom: 1px solid rgba(51,65,85,0.2); }
851
- .dm-attr-name { font-weight: 500; color: var(--text-bright); white-space: nowrap; }
852
- .dm-attr-desc { color: var(--text-muted); }
853
- .dm-relations {
854
- padding: 0.5rem 0.75rem; border-top: 1px solid var(--border);
855
- background: rgba(6,182,212,0.03);
856
- }
857
- .dm-relations-title {
858
- font-size: 0.65rem; text-transform: uppercase; letter-spacing: 0.05em;
859
- color: var(--accent); font-weight: 600; margin-bottom: 0.3rem;
860
- }
861
- .dm-relation-item {
862
- font-size: 0.8rem; color: var(--text); padding: 0.15rem 0;
863
- padding-left: 0.75rem; border-left: 2px solid var(--accent);
864
- margin-bottom: 0.2rem;
865
- }
866
-
867
- /* ============================================
868
- STRUCTURE TAB (Sections/Resources)
869
- ============================================ */
870
- .struct-section {
871
- background: var(--bg-card);
872
- border: 1px solid var(--border);
873
- border-radius: 10px;
874
- margin-bottom: 1rem;
875
- overflow: hidden;
876
- }
877
- .struct-section-header {
878
- display: flex;
879
- align-items: center;
880
- gap: 0.75rem;
881
- padding: 0.75rem 1rem;
882
- background: var(--bg-hover);
883
- border-bottom: 1px solid var(--border);
884
- }
885
- .struct-section-code {
886
- font-weight: 600;
887
- color: var(--text-bright);
888
- font-size: 0.95rem;
889
- }
890
- .struct-section-desc {
891
- flex: 1;
892
- font-size: 0.8rem;
893
- color: var(--text-muted);
894
- }
895
- .struct-section-badge {
896
- font-size: 0.65rem;
897
- color: var(--text-muted);
898
- background: rgba(99,102,241,0.1);
899
- padding: 0.1rem 0.5rem;
900
- border-radius: 4px;
901
- white-space: nowrap;
902
- }
903
- .struct-resources {
904
- padding: 0.5rem 0;
905
- }
906
- .struct-resource {
907
- display: flex;
908
- align-items: baseline;
909
- gap: 0.5rem;
910
- padding: 0.35rem 1rem 0.35rem 1.5rem;
911
- font-size: 0.85rem;
912
- transition: background var(--transition-fast);
913
- }
914
- .struct-resource:hover {
915
- background: var(--bg-hover);
916
- }
917
- .struct-resource-icon {
918
- color: var(--primary-light);
919
- font-size: 0.7rem;
920
- flex-shrink: 0;
921
- }
922
- .struct-resource-name {
923
- font-weight: 500;
924
- color: var(--text-bright);
925
- }
926
- .struct-resource-desc {
927
- font-size: 0.8rem;
928
- color: var(--text-muted);
929
- }
476
+ /* ============================================
477
+ USE CASE LIST
478
+ ============================================ */
479
+ .uc-list { list-style: none; }
480
+
481
+ .uc-item {
482
+ background: var(--bg-card);
483
+ border: 1px solid var(--border);
484
+ border-radius: 8px;
485
+ padding: 1rem 1.25rem;
486
+ margin-bottom: 0.75rem;
487
+ transition: border-color var(--transition-fast);
488
+ }
489
+ .uc-item:hover { border-color: var(--border-light); }
490
+
491
+ .uc-header {
492
+ display: flex;
493
+ align-items: center;
494
+ gap: 0.75rem;
495
+ margin-bottom: 0.5rem;
496
+ }
497
+ .uc-id {
498
+ font-size: 0.7rem;
499
+ font-weight: 700;
500
+ color: var(--primary-light);
501
+ background: rgba(99,102,241,0.1);
502
+ padding: 0.15rem 0.5rem;
503
+ border-radius: 4px;
504
+ }
505
+ .uc-title { font-weight: 600; color: var(--text-bright); flex: 1; }
506
+ .uc-actions { display: flex; gap: 0.3rem; opacity: 0; transition: opacity var(--transition-fast); }
507
+ .uc-item:hover .uc-actions { opacity: 1; }
508
+
509
+ .uc-detail { font-size: 0.875rem; color: var(--text); }
510
+ .uc-detail-label { color: var(--text-muted); font-size: 0.75rem; font-weight: 600; margin-top: 0.5rem; }
511
+
512
+ .uc-actors {
513
+ display: flex; gap: 0.4rem; margin-top: 0.3rem; flex-wrap: wrap;
514
+ }
515
+ .uc-actor {
516
+ font-size: 0.7rem;
517
+ padding: 0.1rem 0.4rem;
518
+ background: rgba(6,182,212,0.1);
519
+ color: var(--accent);
520
+ border-radius: 4px;
521
+ border: 1px solid rgba(6,182,212,0.2);
522
+ }
523
+
524
+ /* ============================================
525
+ BUSINESS RULE LIST
526
+ ============================================ */
527
+ .br-item {
528
+ display: flex;
529
+ align-items: flex-start;
530
+ gap: 0.75rem;
531
+ padding: 0.75rem 1rem;
532
+ background: var(--bg-card);
533
+ border: 1px solid var(--border);
534
+ border-radius: 8px;
535
+ margin-bottom: 0.5rem;
536
+ }
537
+ .br-category {
538
+ font-size: 0.65rem;
539
+ font-weight: 700;
540
+ text-transform: uppercase;
541
+ padding: 0.15rem 0.4rem;
542
+ border-radius: 4px;
543
+ flex-shrink: 0;
544
+ min-width: 70px;
545
+ text-align: center;
546
+ }
547
+ .br-cat-validation { background: rgba(99,102,241,0.15); color: var(--primary-light); }
548
+ .br-cat-calculation { background: rgba(234,179,8,0.15); color: #facc15; }
549
+ .br-cat-workflow { background: rgba(249,115,22,0.15); color: var(--secondary); }
550
+ .br-cat-security { background: rgba(239,68,68,0.15); color: #f87171; }
551
+ .br-cat-data { background: rgba(6,182,212,0.15); color: var(--accent); }
552
+ .br-text { flex: 1; font-size: 0.875rem; }
553
+
554
+ /* ============================================
555
+ STAKEHOLDER TABLE
556
+ ============================================ */
557
+ .stakeholder-grid {
558
+ display: grid;
559
+ grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
560
+ gap: 1rem;
561
+ }
562
+ .stakeholder-card {
563
+ background: var(--bg-card);
564
+ border: 1px solid var(--border);
565
+ border-radius: 10px;
566
+ padding: 1rem;
567
+ }
568
+ .stakeholder-card:hover { border-color: var(--border-light); }
569
+ .stakeholder-role { font-weight: 600; color: var(--text-bright); margin-bottom: 0.25rem; }
570
+ .stakeholder-function { font-size: 0.8rem; color: var(--text-muted); margin-bottom: 0.5rem; }
571
+ .stakeholder-tasks { list-style: none; }
572
+ .stakeholder-tasks li {
573
+ font-size: 0.8rem;
574
+ padding: 0.15rem 0;
575
+ color: var(--text);
576
+ }
577
+ .stakeholder-tasks li::before { content: "- "; color: var(--primary-light); }
578
+ .stakeholder-meta {
579
+ display: flex; gap: 0.75rem; margin-top: 0.5rem; padding-top: 0.5rem;
580
+ border-top: 1px solid var(--border);
581
+ }
582
+ .stakeholder-meta span { font-size: 0.7rem; color: var(--text-muted); }
583
+
584
+ /* ============================================
585
+ PROCESS FLOW
586
+ ============================================ */
587
+ .process-flow {
588
+ display: flex;
589
+ align-items: center;
590
+ gap: 0.25rem;
591
+ overflow-x: auto;
592
+ padding: 1rem 0;
593
+ }
594
+ .process-step {
595
+ background: var(--bg-card);
596
+ border: 1px solid var(--border);
597
+ border-radius: 8px;
598
+ padding: 0.75rem 1rem;
599
+ min-width: 140px;
600
+ text-align: center;
601
+ flex-shrink: 0;
602
+ }
603
+ .process-step:hover { border-color: var(--primary); }
604
+ .process-step-number {
605
+ font-size: 0.65rem;
606
+ color: var(--primary-light);
607
+ font-weight: 700;
608
+ }
609
+ .process-step-label { font-size: 0.8rem; color: var(--text-bright); font-weight: 500; }
610
+ .process-arrow { color: var(--text-muted); font-size: 1.2rem; flex-shrink: 0; }
611
+
612
+ /* ============================================
613
+ display: grid;
614
+ grid-template-columns: auto 1fr auto auto;
615
+ gap: 1rem;
616
+ align-items: center;
617
+ padding: 0.75rem 1rem;
618
+ background: var(--bg-card);
619
+ border: 1px solid var(--border);
620
+ border-radius: 8px;
621
+ margin-bottom: 0.5rem;
622
+ }
623
+ width: 10px; height: 10px;
624
+ border-radius: 50%;
625
+ }
626
+ font-size: 0.7rem;
627
+ color: var(--text-muted);
628
+ text-align: center;
629
+ }
630
+ /* ============================================
631
+ MODULE CARDS (Decomposition)
632
+ ============================================ */
633
+ .module-grid {
634
+ display: grid;
635
+ grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
636
+ gap: 1rem;
637
+ }
638
+ .module-card {
639
+ background: var(--bg-card);
640
+ border: 1px solid var(--border);
641
+ border-radius: 10px;
642
+ padding: 1rem;
643
+ cursor: pointer;
644
+ transition: all var(--transition-fast);
645
+ position: relative;
646
+ }
647
+ .module-card:hover { border-color: var(--primary); }
648
+ .module-card.selected { border-color: var(--primary); box-shadow: 0 0 0 2px rgba(99,102,241,0.2); }
649
+ .module-card-header { display: flex; justify-content: space-between; align-items: start; margin-bottom: 0.5rem; }
650
+ .module-card-code { font-weight: 700; color: var(--text-bright); font-size: 1rem; }
651
+ .module-card-type {
652
+ font-size: 0.65rem; text-transform: uppercase; letter-spacing: 0.05em;
653
+ padding: 0.15rem 0.5rem; border-radius: 4px;
654
+ background: rgba(99,102,241,0.1); color: var(--primary-light);
655
+ }
656
+ .module-card-desc { font-size: 0.8rem; color: var(--text-muted); margin-bottom: 0.5rem; }
657
+ .module-card-meta { display: flex; gap: 0.75rem; font-size: 0.7rem; color: var(--text-muted); }
658
+ .module-card-meta span { display: flex; align-items: center; gap: 0.2rem; }
659
+ .module-card-remove {
660
+ position: absolute; top: 0.5rem; right: 0.5rem;
661
+ background: none; border: none; color: var(--text-muted);
662
+ cursor: pointer; font-size: 0.8rem; opacity: 0;
663
+ transition: opacity var(--transition-fast);
664
+ }
665
+ .module-card:hover .module-card-remove { opacity: 1; }
666
+ .module-card-remove:hover { color: var(--error); }
667
+
668
+ /* ============================================
669
+ TABS (Module Specification)
670
+ ============================================ */
671
+ .tab-bar {
672
+ display: flex;
673
+ gap: 0;
674
+ border-bottom: 1px solid var(--border);
675
+ margin-bottom: 1.5rem;
676
+ overflow-x: auto;
677
+ }
678
+ .tab-btn {
679
+ padding: 0.6rem 1rem;
680
+ background: none;
681
+ border: none;
682
+ border-bottom: 2px solid transparent;
683
+ color: var(--text-muted);
684
+ font-size: 0.8rem;
685
+ font-family: inherit;
686
+ cursor: pointer;
687
+ white-space: nowrap;
688
+ transition: all var(--transition-fast);
689
+ }
690
+ .tab-btn:hover { color: var(--text-bright); background: var(--bg-hover); }
691
+ .tab-btn.active { color: var(--primary-light); border-bottom-color: var(--primary); font-weight: 500; }
692
+ .tab-panel { display: none; }
693
+ .tab-panel.active { display: block; }
694
+
695
+ /* ============================================
696
+ ENTITY TABLE
697
+ ============================================ */
698
+ .entity-block {
699
+ background: var(--bg-card);
700
+ border: 1px solid var(--border);
701
+ border-radius: 10px;
702
+ margin-bottom: 1rem;
703
+ overflow: hidden;
704
+ }
705
+ .entity-header {
706
+ display: flex; align-items: center; justify-content: space-between;
707
+ padding: 0.75rem 1rem;
708
+ background: var(--bg-hover);
709
+ border-bottom: 1px solid var(--border);
710
+ }
711
+ .entity-name { font-weight: 600; color: var(--text-bright); }
712
+ .entity-desc { font-size: 0.8rem; color: var(--text-muted); }
713
+ .attr-table { width: 100%; border-collapse: collapse; }
714
+ .attr-table th {
715
+ text-align: left; font-size: 0.7rem; text-transform: uppercase;
716
+ letter-spacing: 0.05em; color: var(--text-muted); padding: 0.5rem 0.75rem;
717
+ border-bottom: 1px solid var(--border); font-weight: 600;
718
+ }
719
+ .attr-table td {
720
+ padding: 0.5rem 0.75rem; font-size: 0.85rem;
721
+ border-bottom: 1px solid rgba(51,65,85,0.3);
722
+ }
723
+ .attr-required { color: var(--error); font-weight: 700; }
724
+
725
+ /* ============================================
726
+ DEPENDENCY VISUALIZATION
727
+ ============================================ */
728
+ .dep-graph {
729
+ display: flex; flex-direction: column; gap: 1.5rem;
730
+ padding: 1.5rem; background: var(--bg-card); border: 1px solid var(--border);
731
+ border-radius: 10px;
732
+ }
733
+ .dep-layer {
734
+ display: flex; align-items: center; gap: 1rem;
735
+ }
736
+ .dep-layer-label {
737
+ font-size: 0.7rem; color: var(--text-muted); text-transform: uppercase;
738
+ letter-spacing: 0.05em; min-width: 80px; font-weight: 600;
739
+ }
740
+ .dep-layer-modules { display: flex; gap: 0.75rem; flex-wrap: wrap; }
741
+ .dep-module {
742
+ padding: 0.4rem 0.8rem; border-radius: 6px;
743
+ background: rgba(99,102,241,0.1); border: 1px solid rgba(99,102,241,0.3);
744
+ color: var(--primary-light); font-size: 0.8rem; font-weight: 500;
745
+ }
746
+ .dep-arrow {
747
+ text-align: center; color: var(--text-muted); font-size: 1.2rem;
748
+ padding-left: 80px;
749
+ }
750
+
751
+ /* ============================================
752
+ STAT CARDS (Handoff)
753
+ ============================================ */
754
+ .stat-grid {
755
+ display: grid; grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
756
+ gap: 1rem; margin-bottom: 1.5rem;
757
+ }
758
+ .stat-card {
759
+ background: var(--bg-card); border: 1px solid var(--border);
760
+ border-radius: 10px; padding: 1rem; text-align: center;
761
+ }
762
+ .stat-value { font-size: 1.8rem; font-weight: 700; color: var(--text-bright); }
763
+ .stat-label { font-size: 0.75rem; color: var(--text-muted); margin-top: 0.2rem; }
764
+
765
+ /* ============================================
766
+ CONSOLIDATION
767
+ ============================================ */
768
+ .interaction-item {
769
+ display: flex; align-items: center; gap: 0.75rem;
770
+ padding: 0.75rem 1rem; background: var(--bg-card);
771
+ border: 1px solid var(--border); border-radius: 8px;
772
+ margin-bottom: 0.5rem; font-size: 0.85rem;
773
+ }
774
+ .interaction-arrow { color: var(--primary-light); font-weight: 700; font-size: 1.1rem; }
775
+ .interaction-type {
776
+ font-size: 0.65rem; text-transform: uppercase; letter-spacing: 0.05em;
777
+ padding: 0.15rem 0.4rem; border-radius: 4px;
778
+ background: rgba(6,182,212,0.1); color: var(--accent);
779
+ }
780
+ .e2e-flow {
781
+ display: flex; align-items: center; gap: 0.3rem;
782
+ overflow-x: auto; padding: 1rem 0;
783
+ }
784
+ .e2e-step {
785
+ padding: 0.5rem 0.75rem; border-radius: 6px; text-align: center;
786
+ font-size: 0.75rem; flex-shrink: 0;
787
+ background: var(--bg-card); border: 1px solid var(--border);
788
+ }
789
+ .e2e-step-module { font-weight: 600; color: var(--primary-light); font-size: 0.65rem; }
790
+ .e2e-step-action { color: var(--text-bright); }
791
+
792
+ /* ============================================
793
+ DATA MODEL (Consolidation)
794
+ ============================================ */
795
+ .dm-summary {
796
+ display: flex; gap: 1.5rem; margin-bottom: 1.5rem;
797
+ padding: 1rem 1.5rem; background: var(--bg-card);
798
+ border: 1px solid var(--border); border-radius: 10px;
799
+ }
800
+ .dm-summary-item { display: flex; align-items: baseline; gap: 0.4rem; }
801
+ .dm-summary-value { font-size: 1.4rem; font-weight: 700; color: var(--text-bright); }
802
+ .dm-summary-label { font-size: 0.8rem; color: var(--text-muted); }
803
+ .dm-module-group { margin-bottom: 1.5rem; }
804
+ .dm-module-header {
805
+ display: flex; align-items: center; justify-content: space-between;
806
+ padding: 0.5rem 0; margin-bottom: 0.75rem;
807
+ border-bottom: 2px solid var(--primary);
808
+ }
809
+ .dm-module-name { font-weight: 700; color: var(--primary-light); font-size: 1rem; }
810
+ .dm-module-count { font-size: 0.75rem; color: var(--text-muted); }
811
+ .dm-entity-grid {
812
+ display: grid; grid-template-columns: repeat(auto-fill, minmax(340px, 1fr)); gap: 1rem;
813
+ }
814
+ .dm-entity-card {
815
+ background: var(--bg-card); border: 1px solid var(--border);
816
+ border-radius: 10px; overflow: hidden;
817
+ transition: border-color var(--transition-fast);
818
+ }
819
+ .dm-entity-card:hover { border-color: var(--border-light); }
820
+ .dm-entity-header {
821
+ display: flex; align-items: center; justify-content: space-between;
822
+ padding: 0.6rem 0.75rem; background: var(--bg-hover);
823
+ border-bottom: 1px solid var(--border);
824
+ }
825
+ .dm-entity-name { font-weight: 600; color: var(--text-bright); font-size: 0.95rem; }
826
+ .dm-entity-attr-count {
827
+ font-size: 0.65rem; color: var(--text-muted);
828
+ background: rgba(99,102,241,0.1); padding: 0.1rem 0.4rem; border-radius: 4px;
829
+ }
830
+ .dm-entity-desc {
831
+ padding: 0.5rem 0.75rem; font-size: 0.8rem; color: var(--text-muted);
832
+ border-bottom: 1px solid var(--border);
833
+ }
834
+ .dm-attr-table { width: 100%; border-collapse: collapse; }
835
+ .dm-attr-table th {
836
+ text-align: left; font-size: 0.65rem; text-transform: uppercase;
837
+ letter-spacing: 0.05em; color: var(--text-muted); padding: 0.4rem 0.75rem;
838
+ border-bottom: 1px solid var(--border); font-weight: 600;
839
+ }
840
+ .dm-attr-table td { padding: 0.35rem 0.75rem; font-size: 0.8rem; border-bottom: 1px solid rgba(51,65,85,0.2); }
841
+ .dm-attr-name { font-weight: 500; color: var(--text-bright); white-space: nowrap; }
842
+ .dm-attr-desc { color: var(--text-muted); }
843
+ .dm-relations {
844
+ padding: 0.5rem 0.75rem; border-top: 1px solid var(--border);
845
+ background: rgba(6,182,212,0.03);
846
+ }
847
+ .dm-relations-title {
848
+ font-size: 0.65rem; text-transform: uppercase; letter-spacing: 0.05em;
849
+ color: var(--accent); font-weight: 600; margin-bottom: 0.3rem;
850
+ }
851
+ .dm-relation-item {
852
+ font-size: 0.8rem; color: var(--text); padding: 0.15rem 0;
853
+ padding-left: 0.75rem; border-left: 2px solid var(--accent);
854
+ margin-bottom: 0.2rem;
855
+ }
856
+
857
+ /* ============================================
858
+ STRUCTURE TAB (Sections/Resources)
859
+ ============================================ */
860
+ .struct-section {
861
+ background: var(--bg-card);
862
+ border: 1px solid var(--border);
863
+ border-radius: 10px;
864
+ margin-bottom: 1rem;
865
+ overflow: hidden;
866
+ }
867
+ .struct-section-header {
868
+ display: flex;
869
+ align-items: center;
870
+ gap: 0.75rem;
871
+ padding: 0.75rem 1rem;
872
+ background: var(--bg-hover);
873
+ border-bottom: 1px solid var(--border);
874
+ }
875
+ .struct-section-code {
876
+ font-weight: 600;
877
+ color: var(--text-bright);
878
+ font-size: 0.95rem;
879
+ }
880
+ .struct-section-desc {
881
+ flex: 1;
882
+ font-size: 0.8rem;
883
+ color: var(--text-muted);
884
+ }
885
+ .struct-section-badge {
886
+ font-size: 0.65rem;
887
+ color: var(--text-muted);
888
+ background: rgba(99,102,241,0.1);
889
+ padding: 0.1rem 0.5rem;
890
+ border-radius: 4px;
891
+ white-space: nowrap;
892
+ }
893
+ .struct-resources {
894
+ padding: 0.5rem 0;
895
+ }
896
+ .struct-resource {
897
+ display: flex;
898
+ align-items: baseline;
899
+ gap: 0.5rem;
900
+ padding: 0.35rem 1rem 0.35rem 1.5rem;
901
+ font-size: 0.85rem;
902
+ transition: background var(--transition-fast);
903
+ }
904
+ .struct-resource:hover {
905
+ background: var(--bg-hover);
906
+ }
907
+ .struct-resource-icon {
908
+ color: var(--primary-light);
909
+ font-size: 0.7rem;
910
+ flex-shrink: 0;
911
+ }
912
+ .struct-resource-name {
913
+ font-weight: 500;
914
+ color: var(--text-bright);
915
+ }
916
+ .struct-resource-desc {
917
+ font-size: 0.8rem;
918
+ color: var(--text-muted);
919
+ }
930
920
 
931
921
 
932
922
  /* --- 06-wireframes.css --- */
@@ -1959,55 +1949,6 @@ body {
1959
1949
  </div>
1960
1950
  </div>
1961
1951
 
1962
- <!-- SECTION: Risques et hypotheses -->
1963
- <div class="section" id="cadrage-risks" style="display:none;" data-vibe-hide>
1964
- <h2 class="section-title">Risques et hypotheses</h2>
1965
- <p class="section-subtitle">Ce qui pourrait mal tourner et les certitudes non verifiees.</p>
1966
-
1967
- <h3 style="color: var(--text-bright); font-size: 1rem; margin-bottom: 0.75rem;">Risques identifies</h3>
1968
- <div id="risksList"></div>
1969
- <button class="add-btn" onclick="toggleForm('addRiskForm')">+ Ajouter un risque</button>
1970
-
1971
- <div class="inline-form" id="addRiskForm">
1972
- <div class="inline-form-title">Nouveau risque</div>
1973
- <div class="form-group">
1974
- <label class="form-label">Description du risque</label>
1975
- <input type="text" class="form-input" id="risk-desc" placeholder="Qu'est-ce qui pourrait mal tourner ?">
1976
- </div>
1977
- <div class="form-row">
1978
- <div class="form-group">
1979
- <label class="form-label">Probabilite</label>
1980
- <select class="form-select" id="risk-probability">
1981
- <option value="high">Forte</option>
1982
- <option value="medium">Moyenne</option>
1983
- <option value="low">Faible</option>
1984
- </select>
1985
- </div>
1986
- <div class="form-group">
1987
- <label class="form-label">Impact</label>
1988
- <select class="form-select" id="risk-impact">
1989
- <option value="high">Grave</option>
1990
- <option value="medium">Moyen</option>
1991
- <option value="low">Faible</option>
1992
- </select>
1993
- </div>
1994
- </div>
1995
- <div class="form-group">
1996
- <label class="form-label">Mesure de prevention ou de reduction</label>
1997
- <textarea class="form-textarea" id="risk-mitigation" placeholder="Comment prevenir ou reduire ce risque ?"></textarea>
1998
- </div>
1999
- <div class="form-actions">
2000
- <button class="btn" onclick="toggleForm('addRiskForm')">Annuler</button>
2001
- <button class="btn btn-primary" onclick="addRisk()">Ajouter ce risque</button>
2002
- </div>
2003
- </div>
2004
-
2005
- <h3 style="color: var(--text-bright); font-size: 1rem; margin: 2rem 0 0.75rem;">Hypotheses a verifier</h3>
2006
- <div class="card">
2007
- <div class="editable" contenteditable="true" data-field="risks.assumptions" data-placeholder="Quelles hypotheses faites-vous sur ce projet sans les avoir verifiees ? (une par ligne)"></div>
2008
- </div>
2009
- </div>
2010
-
2011
1952
  <!-- SECTION: Criteres de reussite -->
2012
1953
  <div class="section" id="cadrage-success" style="display:none;">
2013
1954
  <h2 class="section-title">Criteres de reussite</h2>
@@ -2295,7 +2236,6 @@ data.specComments = data.specComments || {};
2295
2236
  data.customRoles = data.customRoles || [];
2296
2237
  data.customActions = data.customActions || [];
2297
2238
  data.cadrage.criteria = data.cadrage.criteria || [];
2298
- data.cadrage.risks = data.cadrage.risks || [];
2299
2239
  data.consolidation = data.consolidation || {};
2300
2240
  data.consolidation.e2eFlows = data.consolidation.e2eFlows || [];
2301
2241
 
@@ -2431,7 +2371,6 @@ document.addEventListener('DOMContentLoaded', function() {
2431
2371
  }
2432
2372
  renderStakeholders();
2433
2373
  renderScope();
2434
- renderRisks();
2435
2374
  renderCriteria();
2436
2375
  renderModules();
2437
2376
  renderDependencies();
@@ -2443,6 +2382,7 @@ document.addEventListener('DOMContentLoaded', function() {
2443
2382
  updateDepSelects();
2444
2383
  initInlineComments();
2445
2384
  renderReviewPanel();
2385
+ showSection(currentSectionId);
2446
2386
  });
2447
2387
 
2448
2388
 
@@ -2562,7 +2502,6 @@ function buildCadrageItems() {
2562
2502
  return renderNavItem('cadrage-context', 'Contexte') +
2563
2503
  renderNavItem('cadrage-stakeholders', 'Parties prenantes', data.cadrage.stakeholders.length) +
2564
2504
  renderNavItem('cadrage-scope', 'Perimetre fonctionnel') +
2565
- (isVibeCoding ? '' : renderNavItem('cadrage-risks', 'Risques et hypotheses', data.cadrage.risks.length)) +
2566
2505
  renderNavItem('cadrage-success', 'Criteres de reussite');
2567
2506
  }
2568
2507
 
@@ -2839,53 +2778,14 @@ function toggleCriterion(index) {
2839
2778
  function renderCriteria() {
2840
2779
  const container = document.getElementById('criteriaList');
2841
2780
  if (!container) return;
2842
- const criteria = data.cadrage.criteria || [];
2843
- container.innerHTML = criteria.map((c, i) => `
2844
- <div class="uc-item" style="display:flex;align-items:center;gap:0.75rem;">
2845
- <input type="checkbox" ${c.validated ? 'checked' : ''} onchange="toggleCriterion(${i})" style="cursor:pointer;width:18px;height:18px;flex-shrink:0;">
2846
- <span style="flex:1;${c.validated ? 'text-decoration:line-through;color:var(--text-muted);' : ''}">${c.text}</span>
2847
- <button class="btn btn-sm" onclick="removeCriterion(${i})" style="opacity:0.5;flex-shrink:0;">&#10005;</button>
2848
- </div>
2849
- `).join('');
2850
- }
2851
-
2852
- /* ============================================
2853
- RISKS
2854
- ============================================ */
2855
- function addRisk() {
2856
- const desc = document.getElementById('risk-desc').value.trim();
2857
- if (!desc) return;
2858
-
2859
- data.cadrage.risks.push({
2860
- description: desc,
2861
- probability: document.getElementById('risk-probability').value,
2862
- impact: document.getElementById('risk-impact').value,
2863
- mitigation: document.getElementById('risk-mitigation').value.trim()
2864
- });
2865
-
2866
- renderRisks();
2867
- toggleForm('addRiskForm');
2868
- clearForm('addRiskForm');
2869
- autoSave();
2870
- }
2871
-
2872
- function renderRisks() {
2873
- const list = document.getElementById('risksList');
2874
- list.innerHTML = data.cadrage.risks.map((r, i) => {
2875
- const level = (r.probability === 'high' && r.impact === 'high') ? 'critical'
2876
- : (r.probability === 'low' && r.impact === 'low') ? 'low' : 'medium';
2877
- return `
2878
- <div class="risk-item">
2879
- <div class="risk-level risk-${level}"></div>
2880
- <div>
2881
- <div class="risk-text">${r.description}</div>
2882
- ${r.mitigation ? '<div style="font-size:0.75rem;color:var(--text-muted);margin-top:0.25rem;">Prevention : ' + r.mitigation + '</div>' : ''}
2883
- </div>
2884
- <div class="risk-probability">${formatLevel(r.probability)}</div>
2885
- <div class="risk-impact">${formatLevel(r.impact)}</div>
2886
- </div>
2887
- `;
2888
- }).join('');
2781
+ const criteria = data.cadrage.criteria || [];
2782
+ container.innerHTML = criteria.map((c, i) => `
2783
+ <div class="uc-item" style="display:flex;align-items:center;gap:0.75rem;">
2784
+ <input type="checkbox" ${c.validated ? 'checked' : ''} onchange="toggleCriterion(${i})" style="cursor:pointer;width:18px;height:18px;flex-shrink:0;">
2785
+ <span style="flex:1;${c.validated ? 'text-decoration:line-through;color:var(--text-muted);' : ''}">${c.text}</span>
2786
+ <button class="btn btn-sm" onclick="removeCriterion(${i})" style="opacity:0.5;flex-shrink:0;">&#10005;</button>
2787
+ </div>
2788
+ `).join('');
2889
2789
  }
2890
2790
 
2891
2791
  /* ============================================
@@ -3602,7 +3502,6 @@ function renderWireframeMockup(code, wf, i) {
3602
3502
  </div>
3603
3503
  `;
3604
3504
  }
3605
- }
3606
3505
 
3607
3506
  function toggleWireframeView(wireframeId, view) {
3608
3507
  const container = document.getElementById(wireframeId);
@@ -4399,7 +4298,6 @@ function renderHandoffStats() {
4399
4298
  <div class="stat-card"><div class="stat-value">${totalStakeholders}</div><div class="stat-label">Profils utilisateurs</div></div>
4400
4299
  <div class="stat-card"><div class="stat-value">${data.dependencies.length}</div><div class="stat-label">Dependances</div></div>
4401
4300
  <div class="stat-card"><div class="stat-value">${data.consolidation.e2eFlows.length}</div><div class="stat-label">Parcours bout en bout</div></div>
4402
- ${isVibeCoding ? '' : `<div class="stat-card"><div class="stat-value">${data.cadrage.risks.length}</div><div class="stat-label">Risques identifies</div></div>`}
4403
4301
  `;
4404
4302
  }
4405
4303
 
@@ -4468,141 +4366,139 @@ function renderCoverageMatrix() {
4468
4366
 
4469
4367
 
4470
4368
  /* --- 08-editing.js --- */
4471
- /* ============================================
4472
- PERSISTENCE
4473
- ============================================ */
4474
- function autoSave() {
4475
- data.metadata.lastModified = new Date().toISOString();
4476
- localStorage.setItem(APP_KEY, JSON.stringify(data));
4477
- }
4478
-
4479
- function saveToLocalStorage() {
4480
- autoSave();
4481
- showNotification('Modifications sauvegardees');
4482
- }
4483
-
4484
- function resetToEmbedded() {
4485
- if (!confirm('Reinitialiser toutes les donnees depuis la version d\'origine ? Vos modifications locales (commentaires, notes) seront perdues.')) return;
4486
- localStorage.removeItem(APP_KEY);
4487
- // Restore data from ORIGINAL_DATA
4488
- Object.keys(data).forEach(k => delete data[k]);
4489
- Object.assign(data, JSON.parse(JSON.stringify(ORIGINAL_DATA)));
4490
- // Re-render everything
4491
- initEditableFields();
4492
- renderStakeholders();
4493
- renderScope();
4494
- renderRisks();
4495
- renderCriteria();
4496
- renderModules();
4497
- renderDependencies();
4498
- renderAllModuleSpecs();
4499
- renderConsolidation();
4500
- renderHandoff();
4501
- renderE2EFlows();
4502
- updateCounts();
4503
- renderReviewPanel();
4504
- showNotification('Donnees reinitialisees depuis la version d\'origine');
4505
- }
4506
-
4507
- function loadFromLocalStorage() {
4508
- const saved = localStorage.getItem(APP_KEY);
4509
- if (!saved) return;
4510
-
4511
- try {
4512
- const parsed = JSON.parse(saved);
4513
-
4514
- // Build fingerprint of embedded structural data to detect HTML regeneration
4515
- const embeddedFingerprint = ORIGINAL_DATA.modules.map(m => m.code).sort().join(',')
4516
- + '|' + (ORIGINAL_DATA.metadata?.createdAt || '')
4517
- + '|' + ORIGINAL_DATA.modules.length;
4518
- const cachedFingerprint = parsed._structureFingerprint || '';
4519
-
4520
- const structureChanged = embeddedFingerprint !== cachedFingerprint;
4521
- const embeddedModuleCount = ORIGINAL_DATA.modules?.length || 0;
4522
- const cachedModuleCount = (parsed.modules || []).length;
4523
- const hasNewModules = embeddedModuleCount > cachedModuleCount;
4524
- const embeddedModuleCodes = new Set(ORIGINAL_DATA.modules.map(m => m.code));
4525
- const cachedModuleCodes = new Set((parsed.modules || []).map(m => m.code));
4526
- const missingModules = [...embeddedModuleCodes].filter(c => !cachedModuleCodes.has(c));
4527
-
4528
- if (structureChanged || hasNewModules || missingModules.length > 0) {
4529
- // HTML was regenerated or has new modules — keep embedded structural data
4530
- // Only restore user-specific edits (comments, custom roles, notes)
4531
- data.wireframeComments = parsed.wireframeComments || {};
4532
- data.specComments = parsed.specComments || {};
4533
- data.customRoles = parsed.customRoles || [];
4534
- data.customActions = parsed.customActions || [];
4535
- data.comments = parsed.comments || [];
4536
-
4537
- // Merge user-added notes per module (preserve existing module notes)
4538
- for (const code of Object.keys(parsed.moduleSpecs || {})) {
4539
- if (data.moduleSpecs[code] && parsed.moduleSpecs[code]?.notes) {
4540
- data.moduleSpecs[code].notes = parsed.moduleSpecs[code].notes;
4541
- }
4542
- }
4543
-
4544
- // Save fresh embedded data with fingerprint
4545
- data._structureFingerprint = embeddedFingerprint;
4546
- autoSave();
4547
- } else {
4548
- // Cache matches current structure safe to restore user edits on cadrage/notes
4549
- // IMPORTANT: Always keep embedded modules and moduleSpecs as structural source of truth
4550
- // Only merge cadrage user-editable fields and notes
4551
- if (parsed.cadrage) {
4552
- // Merge cadrage context (user may have edited text fields)
4553
- if (parsed.cadrage.context) {
4554
- data.cadrage.context = { ...data.cadrage.context, ...parsed.cadrage.context };
4555
- }
4556
- // Merge scope only if user added items interactively
4557
- if (parsed.cadrage.scope) {
4558
- data.cadrage.scope = { ...data.cadrage.scope, ...parsed.cadrage.scope };
4559
- }
4560
- // Merge stakeholders and risks if user edited them
4561
- if (parsed.cadrage.stakeholders) data.cadrage.stakeholders = parsed.cadrage.stakeholders;
4562
- if (parsed.cadrage.risks) data.cadrage.risks = parsed.cadrage.risks;
4563
- }
4564
- data.dependencies = parsed.dependencies || data.dependencies;
4565
- data.consolidation = { ...data.consolidation, ...(parsed.consolidation || {}) };
4566
-
4567
- // Merge moduleSpecs: keep embedded structure, overlay user-editable fields (notes, custom permissions)
4568
- for (const code of Object.keys(data.moduleSpecs)) {
4569
- const cached = parsed.moduleSpecs?.[code];
4570
- if (cached) {
4571
- // Preserve user notes
4572
- if (cached.notes) data.moduleSpecs[code].notes = cached.notes;
4573
- // Preserve user-added use cases/rules/entities (merged with embedded)
4574
- // Only add items that don't exist in embedded (by name match)
4575
- const embeddedUcNames = new Set((data.moduleSpecs[code].useCases || []).map(uc => uc.name));
4576
- (cached.useCases || []).forEach(uc => {
4577
- if (!embeddedUcNames.has(uc.name)) data.moduleSpecs[code].useCases.push(uc);
4578
- });
4579
- const embeddedBrNames = new Set((data.moduleSpecs[code].businessRules || []).map(br => br.name));
4580
- (cached.businessRules || []).forEach(br => {
4581
- if (!embeddedBrNames.has(br.name)) data.moduleSpecs[code].businessRules.push(br);
4582
- });
4583
- // Preserve user permissions edits
4584
- if (cached.permissions) data.moduleSpecs[code].permissions = cached.permissions;
4585
- }
4586
- }
4587
-
4588
- data.wireframeComments = parsed.wireframeComments || {};
4589
- data.specComments = parsed.specComments || {};
4590
- data.customRoles = parsed.customRoles || [];
4591
- data.customActions = parsed.customActions || [];
4592
- data.comments = parsed.comments || [];
4593
-
4594
- // Update fingerprint
4595
- data._structureFingerprint = embeddedFingerprint;
4596
- autoSave();
4597
- }
4598
-
4599
- // Restore editable fields
4600
- document.querySelectorAll('.editable[data-field]').forEach(el => {
4601
- const value = getNestedValue(data, 'cadrage.' + el.dataset.field);
4602
- if (value) el.textContent = value;
4603
- });
4604
- } catch (e) { console.error('Error loading saved data:', e); }
4605
- }
4369
+ /* ============================================
4370
+ PERSISTENCE
4371
+ ============================================ */
4372
+ function autoSave() {
4373
+ data.metadata.lastModified = new Date().toISOString();
4374
+ localStorage.setItem(APP_KEY, JSON.stringify(data));
4375
+ }
4376
+
4377
+ function saveToLocalStorage() {
4378
+ autoSave();
4379
+ showNotification('Modifications sauvegardees');
4380
+ }
4381
+
4382
+ function resetToEmbedded() {
4383
+ if (!confirm('Reinitialiser toutes les donnees depuis la version d\'origine ? Vos modifications locales (commentaires, notes) seront perdues.')) return;
4384
+ localStorage.removeItem(APP_KEY);
4385
+ // Restore data from ORIGINAL_DATA
4386
+ Object.keys(data).forEach(k => delete data[k]);
4387
+ Object.assign(data, JSON.parse(JSON.stringify(ORIGINAL_DATA)));
4388
+ // Re-render everything
4389
+ initEditableFields();
4390
+ renderStakeholders();
4391
+ renderScope();
4392
+ renderCriteria();
4393
+ renderModules();
4394
+ renderDependencies();
4395
+ renderAllModuleSpecs();
4396
+ renderConsolidation();
4397
+ renderHandoff();
4398
+ renderE2EFlows();
4399
+ updateCounts();
4400
+ renderReviewPanel();
4401
+ showNotification('Donnees reinitialisees depuis la version d\'origine');
4402
+ }
4403
+
4404
+ function loadFromLocalStorage() {
4405
+ const saved = localStorage.getItem(APP_KEY);
4406
+ if (!saved) return;
4407
+
4408
+ try {
4409
+ const parsed = JSON.parse(saved);
4410
+
4411
+ // Build fingerprint of embedded structural data to detect HTML regeneration
4412
+ const embeddedFingerprint = ORIGINAL_DATA.modules.map(m => m.code).sort().join(',')
4413
+ + '|' + (ORIGINAL_DATA.metadata?.createdAt || '')
4414
+ + '|' + ORIGINAL_DATA.modules.length;
4415
+ const cachedFingerprint = parsed._structureFingerprint || '';
4416
+
4417
+ const structureChanged = embeddedFingerprint !== cachedFingerprint;
4418
+ const embeddedModuleCount = ORIGINAL_DATA.modules?.length || 0;
4419
+ const cachedModuleCount = (parsed.modules || []).length;
4420
+ const hasNewModules = embeddedModuleCount > cachedModuleCount;
4421
+ const embeddedModuleCodes = new Set(ORIGINAL_DATA.modules.map(m => m.code));
4422
+ const cachedModuleCodes = new Set((parsed.modules || []).map(m => m.code));
4423
+ const missingModules = [...embeddedModuleCodes].filter(c => !cachedModuleCodes.has(c));
4424
+
4425
+ if (structureChanged || hasNewModules || missingModules.length > 0) {
4426
+ // HTML was regenerated or has new modules — keep embedded structural data
4427
+ // Only restore user-specific edits (comments, custom roles, notes)
4428
+ data.wireframeComments = parsed.wireframeComments || {};
4429
+ data.specComments = parsed.specComments || {};
4430
+ data.customRoles = parsed.customRoles || [];
4431
+ data.customActions = parsed.customActions || [];
4432
+ data.comments = parsed.comments || [];
4433
+
4434
+ // Merge user-added notes per module (preserve existing module notes)
4435
+ for (const code of Object.keys(parsed.moduleSpecs || {})) {
4436
+ if (data.moduleSpecs[code] && parsed.moduleSpecs[code]?.notes) {
4437
+ data.moduleSpecs[code].notes = parsed.moduleSpecs[code].notes;
4438
+ }
4439
+ }
4440
+
4441
+ // Save fresh embedded data with fingerprint
4442
+ data._structureFingerprint = embeddedFingerprint;
4443
+ autoSave();
4444
+ } else {
4445
+ // Cache matches current structure — safe to restore user edits on cadrage/notes
4446
+ // IMPORTANT: Always keep embedded modules and moduleSpecs as structural source of truth
4447
+ // Only merge cadrage user-editable fields and notes
4448
+ if (parsed.cadrage) {
4449
+ // Merge cadrage context (user may have edited text fields)
4450
+ if (parsed.cadrage.context) {
4451
+ data.cadrage.context = { ...data.cadrage.context, ...parsed.cadrage.context };
4452
+ }
4453
+ // Merge scope only if user added items interactively
4454
+ if (parsed.cadrage.scope) {
4455
+ data.cadrage.scope = { ...data.cadrage.scope, ...parsed.cadrage.scope };
4456
+ }
4457
+ // Merge stakeholders if user edited them
4458
+ if (parsed.cadrage.stakeholders) data.cadrage.stakeholders = parsed.cadrage.stakeholders;
4459
+ }
4460
+ data.dependencies = parsed.dependencies || data.dependencies;
4461
+ data.consolidation = { ...data.consolidation, ...(parsed.consolidation || {}) };
4462
+
4463
+ // Merge moduleSpecs: keep embedded structure, overlay user-editable fields (notes, custom permissions)
4464
+ for (const code of Object.keys(data.moduleSpecs)) {
4465
+ const cached = parsed.moduleSpecs?.[code];
4466
+ if (cached) {
4467
+ // Preserve user notes
4468
+ if (cached.notes) data.moduleSpecs[code].notes = cached.notes;
4469
+ // Preserve user-added use cases/rules/entities (merged with embedded)
4470
+ // Only add items that don't exist in embedded (by name match)
4471
+ const embeddedUcNames = new Set((data.moduleSpecs[code].useCases || []).map(uc => uc.name));
4472
+ (cached.useCases || []).forEach(uc => {
4473
+ if (!embeddedUcNames.has(uc.name)) data.moduleSpecs[code].useCases.push(uc);
4474
+ });
4475
+ const embeddedBrNames = new Set((data.moduleSpecs[code].businessRules || []).map(br => br.name));
4476
+ (cached.businessRules || []).forEach(br => {
4477
+ if (!embeddedBrNames.has(br.name)) data.moduleSpecs[code].businessRules.push(br);
4478
+ });
4479
+ // Preserve user permissions edits
4480
+ if (cached.permissions) data.moduleSpecs[code].permissions = cached.permissions;
4481
+ }
4482
+ }
4483
+
4484
+ data.wireframeComments = parsed.wireframeComments || {};
4485
+ data.specComments = parsed.specComments || {};
4486
+ data.customRoles = parsed.customRoles || [];
4487
+ data.customActions = parsed.customActions || [];
4488
+ data.comments = parsed.comments || [];
4489
+
4490
+ // Update fingerprint
4491
+ data._structureFingerprint = embeddedFingerprint;
4492
+ autoSave();
4493
+ }
4494
+
4495
+ // Restore editable fields
4496
+ document.querySelectorAll('.editable[data-field]').forEach(el => {
4497
+ const value = getNestedValue(data, 'cadrage.' + el.dataset.field);
4498
+ if (value) el.textContent = value;
4499
+ });
4500
+ } catch (e) { console.error('Error loading saved data:', e); }
4501
+ }
4606
4502
 
4607
4503
 
4608
4504
  /* --- 09-export.js --- */
@@ -4777,374 +4673,373 @@ function saveReviewJSON() {
4777
4673
 
4778
4674
 
4779
4675
  /* --- 10-comments.js --- */
4780
- /* ============================================
4781
- INLINE COMMENTS SYSTEM
4782
- ============================================ */
4783
-
4784
- /**
4785
- * Comments are stored in data.comments[] with structure:
4786
- * { id, sectionId, cardIndex, author, timestamp, content, status, category }
4787
- *
4788
- * - sectionId: matches the section div id (e.g. "cadrage-problem", "module-spec-Clients")
4789
- * - cardIndex: index of the card within that section (0-based)
4790
- * - status: "to-review" | "validated"
4791
- * - category: "clarification" | "correction" | "suggestion"
4792
- */
4793
-
4794
- function initInlineComments() {
4795
- // Cadrage sections: direct card children
4796
- document.querySelectorAll('.section > .card, .section > .stakeholder-card, .section > .uc-item, .section > .risk-item').forEach(function(card) {
4797
- if (card.dataset.commentInitialized) return;
4798
- card.dataset.commentInitialized = 'true';
4799
- var section = card.closest('.section');
4800
- var sectionId = section ? section.id : 'unknown';
4801
- var siblings = Array.from(section.querySelectorAll(':scope > .card, :scope > .stakeholder-card, :scope > .uc-item, :scope > .risk-item'));
4802
- var index = siblings.indexOf(card);
4803
- card.appendChild(createCommentUI(sectionId, index));
4804
- });
4805
-
4806
- // Module spec lists: nested items in ucList, brList, entList containers
4807
- document.querySelectorAll('[id^="ucList-"] > .uc-item, [id^="brList-"] > div, [id^="entList-"] > .entity-block').forEach(function(item) {
4808
- if (item.dataset.commentInitialized) return;
4809
- item.dataset.commentInitialized = 'true';
4810
- var list = item.parentElement;
4811
- var listId = list.id;
4812
- var siblings = Array.from(list.children);
4813
- var index = siblings.indexOf(item);
4814
- item.appendChild(createCommentUI(listId, index));
4815
- });
4816
-
4817
- // Stakeholder cards in grid
4818
- document.querySelectorAll('.stakeholder-grid > .stakeholder-card').forEach(function(card) {
4819
- if (card.dataset.commentInitialized) return;
4820
- card.dataset.commentInitialized = 'true';
4821
- var grid = card.parentElement;
4822
- var section = card.closest('.section');
4823
- var sectionId = section ? section.id : 'stakeholders';
4824
- var siblings = Array.from(grid.children);
4825
- var index = siblings.indexOf(card);
4826
- card.appendChild(createCommentUI(sectionId, index));
4827
- });
4828
-
4829
- // Scope items
4830
- ['scopeVital', 'scopeImportant', 'scopeOptional', 'scopeExcluded'].forEach(function(containerId) {
4831
- var container = document.getElementById(containerId);
4832
- if (!container) return;
4833
- container.querySelectorAll('.uc-item').forEach(function(item, index) {
4834
- if (item.dataset.commentInitialized) return;
4835
- item.dataset.commentInitialized = 'true';
4836
- item.appendChild(createCommentUI(containerId, index));
4837
- });
4838
- });
4839
- }
4840
-
4841
- function createCommentUI(sectionId, cardIndex) {
4842
- const comments = getCommentsForCard(sectionId, cardIndex);
4843
- const count = comments.length;
4844
-
4845
- const container = document.createElement('div');
4846
- container.className = 'comment-btn-container';
4847
- container.dataset.sectionId = sectionId;
4848
- container.dataset.cardIndex = cardIndex;
4849
-
4850
- container.innerHTML = `
4851
- <button class="comment-toggle-btn" onclick="toggleCommentThread('${sectionId}', ${cardIndex})">
4852
- Commentaires <span class="comment-count ${count === 0 ? 'empty' : ''}">${count}</span>
4853
- </button>
4854
- <div class="comment-thread" id="comment-thread-${sectionId}-${cardIndex}">
4855
- <div class="comment-items" id="comment-items-${sectionId}-${cardIndex}">
4856
- ${renderCommentItems(sectionId, cardIndex)}
4857
- </div>
4858
- <div class="comment-add-form">
4859
- <textarea id="comment-text-${sectionId}-${cardIndex}" placeholder="Ajouter un commentaire..."></textarea>
4860
- <select id="comment-cat-${sectionId}-${cardIndex}">
4861
- <option value="clarification">Clarification</option>
4862
- <option value="correction">Correction</option>
4863
- <option value="suggestion">Suggestion</option>
4864
- </select>
4865
- <button onclick="addInlineComment('${sectionId}', ${cardIndex})">Ajouter</button>
4866
- </div>
4867
- </div>
4868
- `;
4869
-
4870
- return container;
4871
- }
4872
-
4873
- function getCommentsForCard(sectionId, cardIndex) {
4874
- return (data.comments || []).filter(c =>
4875
- c.sectionId === sectionId && c.cardIndex === cardIndex
4876
- );
4877
- }
4878
-
4879
- function toggleCommentThread(sectionId, cardIndex) {
4880
- const thread = document.getElementById('comment-thread-' + sectionId + '-' + cardIndex);
4881
- if (thread) thread.classList.toggle('visible');
4882
- }
4883
-
4884
- function renderCommentItems(sectionId, cardIndex) {
4885
- const comments = getCommentsForCard(sectionId, cardIndex);
4886
- if (comments.length === 0) {
4887
- return '<div style="font-size:0.8rem;color:var(--text-muted);padding:0.5rem 0;font-style:italic;">Aucun commentaire</div>';
4888
- }
4889
-
4890
- return comments.map((c, i) => {
4891
- const initials = (c.author || 'U').substring(0, 2).toUpperCase();
4892
- const date = c.timestamp ? new Date(c.timestamp).toLocaleDateString('fr-FR', { day: '2-digit', month: '2-digit', hour: '2-digit', minute: '2-digit' }) : '';
4893
- const globalIndex = data.comments.indexOf(c);
4894
-
4895
- return `
4896
- <div class="comment-item">
4897
- <div class="comment-avatar">${initials}</div>
4898
- <div class="comment-body">
4899
- <div class="comment-meta">
4900
- <span class="comment-author">${c.author || 'Utilisateur'}</span>
4901
- <span class="comment-date">${date}</span>
4902
- <span class="comment-category comment-category-${c.category}">${c.category}</span>
4903
- <span class="comment-status comment-status-${c.status}">${c.status === 'validated' ? 'Valide' : 'A revoir'}</span>
4904
- </div>
4905
- <div class="comment-text">${c.content}</div>
4906
- <div class="comment-actions">
4907
- <button class="comment-action-btn" onclick="toggleCommentStatus(${globalIndex})">${c.status === 'validated' ? 'Remettre a revoir' : 'Valider'}</button>
4908
- <button class="comment-action-btn" onclick="deleteComment(${globalIndex})" style="color:var(--error);">Supprimer</button>
4909
- </div>
4910
- </div>
4911
- </div>
4912
- `;
4913
- }).join('');
4914
- }
4915
-
4916
- function addInlineComment(sectionId, cardIndex) {
4917
- const textEl = document.getElementById('comment-text-' + sectionId + '-' + cardIndex);
4918
- const catEl = document.getElementById('comment-cat-' + sectionId + '-' + cardIndex);
4919
- const content = textEl.value.trim();
4920
- if (!content) return;
4921
-
4922
- const comment = {
4923
- id: 'comment-' + Date.now(),
4924
- sectionId: sectionId,
4925
- cardIndex: cardIndex,
4926
- author: 'Utilisateur',
4927
- timestamp: new Date().toISOString(),
4928
- content: content,
4929
- status: 'to-review',
4930
- category: catEl.value
4931
- };
4932
-
4933
- data.comments.push(comment);
4934
- textEl.value = '';
4935
-
4936
- refreshCommentUI(sectionId, cardIndex);
4937
- renderReviewPanel();
4938
- autoSave();
4939
- }
4940
-
4941
- function toggleCommentStatus(globalIndex) {
4942
- const comment = data.comments[globalIndex];
4943
- if (!comment) return;
4944
- comment.status = comment.status === 'validated' ? 'to-review' : 'validated';
4945
- refreshCommentUI(comment.sectionId, comment.cardIndex);
4946
- renderReviewPanel();
4947
- autoSave();
4948
- }
4949
-
4950
- function deleteComment(globalIndex) {
4951
- const comment = data.comments[globalIndex];
4952
- if (!comment) return;
4953
- const sectionId = comment.sectionId;
4954
- const cardIndex = comment.cardIndex;
4955
- data.comments.splice(globalIndex, 1);
4956
- refreshCommentUI(sectionId, cardIndex);
4957
- renderReviewPanel();
4958
- autoSave();
4959
- }
4960
-
4961
- function refreshCommentUI(sectionId, cardIndex) {
4962
- // Update comment items
4963
- const itemsContainer = document.getElementById('comment-items-' + sectionId + '-' + cardIndex);
4964
- if (itemsContainer) {
4965
- itemsContainer.innerHTML = renderCommentItems(sectionId, cardIndex);
4966
- }
4967
-
4968
- // Update count badge
4969
- const count = getCommentsForCard(sectionId, cardIndex).length;
4970
- const container = document.querySelector(`.comment-btn-container[data-section-id="${sectionId}"][data-card-index="${cardIndex}"]`);
4971
- if (container) {
4972
- const badge = container.querySelector('.comment-count');
4973
- if (badge) {
4974
- badge.textContent = count;
4975
- badge.className = 'comment-count' + (count === 0 ? ' empty' : '');
4976
- }
4977
- }
4978
- }
4676
+ /* ============================================
4677
+ INLINE COMMENTS SYSTEM
4678
+ ============================================ */
4679
+
4680
+ /**
4681
+ * Comments are stored in data.comments[] with structure:
4682
+ * { id, sectionId, cardIndex, author, timestamp, content, status, category }
4683
+ *
4684
+ * - sectionId: matches the section div id (e.g. "cadrage-problem", "module-spec-Clients")
4685
+ * - cardIndex: index of the card within that section (0-based)
4686
+ * - status: "to-review" | "validated"
4687
+ * - category: "clarification" | "correction" | "suggestion"
4688
+ */
4689
+
4690
+ function initInlineComments() {
4691
+ // Cadrage sections: direct card children
4692
+ document.querySelectorAll('.section > .card, .section > .stakeholder-card, .section > .uc-item').forEach(function(card) {
4693
+ if (card.dataset.commentInitialized) return;
4694
+ card.dataset.commentInitialized = 'true';
4695
+ var section = card.closest('.section');
4696
+ var sectionId = section ? section.id : 'unknown';
4697
+ var siblings = Array.from(section.querySelectorAll(':scope > .card, :scope > .stakeholder-card, :scope > .uc-item'));
4698
+ var index = siblings.indexOf(card);
4699
+ card.appendChild(createCommentUI(sectionId, index));
4700
+ });
4701
+
4702
+ // Module spec lists: nested items in ucList, brList, entList containers
4703
+ document.querySelectorAll('[id^="ucList-"] > .uc-item, [id^="brList-"] > div, [id^="entList-"] > .entity-block').forEach(function(item) {
4704
+ if (item.dataset.commentInitialized) return;
4705
+ item.dataset.commentInitialized = 'true';
4706
+ var list = item.parentElement;
4707
+ var listId = list.id;
4708
+ var siblings = Array.from(list.children);
4709
+ var index = siblings.indexOf(item);
4710
+ item.appendChild(createCommentUI(listId, index));
4711
+ });
4712
+
4713
+ // Stakeholder cards in grid
4714
+ document.querySelectorAll('.stakeholder-grid > .stakeholder-card').forEach(function(card) {
4715
+ if (card.dataset.commentInitialized) return;
4716
+ card.dataset.commentInitialized = 'true';
4717
+ var grid = card.parentElement;
4718
+ var section = card.closest('.section');
4719
+ var sectionId = section ? section.id : 'stakeholders';
4720
+ var siblings = Array.from(grid.children);
4721
+ var index = siblings.indexOf(card);
4722
+ card.appendChild(createCommentUI(sectionId, index));
4723
+ });
4724
+
4725
+ // Scope items
4726
+ ['scopeVital', 'scopeImportant', 'scopeOptional', 'scopeExcluded'].forEach(function(containerId) {
4727
+ var container = document.getElementById(containerId);
4728
+ if (!container) return;
4729
+ container.querySelectorAll('.uc-item').forEach(function(item, index) {
4730
+ if (item.dataset.commentInitialized) return;
4731
+ item.dataset.commentInitialized = 'true';
4732
+ item.appendChild(createCommentUI(containerId, index));
4733
+ });
4734
+ });
4735
+ }
4736
+
4737
+ function createCommentUI(sectionId, cardIndex) {
4738
+ const comments = getCommentsForCard(sectionId, cardIndex);
4739
+ const count = comments.length;
4740
+
4741
+ const container = document.createElement('div');
4742
+ container.className = 'comment-btn-container';
4743
+ container.dataset.sectionId = sectionId;
4744
+ container.dataset.cardIndex = cardIndex;
4745
+
4746
+ container.innerHTML = `
4747
+ <button class="comment-toggle-btn" onclick="toggleCommentThread('${sectionId}', ${cardIndex})">
4748
+ Commentaires <span class="comment-count ${count === 0 ? 'empty' : ''}">${count}</span>
4749
+ </button>
4750
+ <div class="comment-thread" id="comment-thread-${sectionId}-${cardIndex}">
4751
+ <div class="comment-items" id="comment-items-${sectionId}-${cardIndex}">
4752
+ ${renderCommentItems(sectionId, cardIndex)}
4753
+ </div>
4754
+ <div class="comment-add-form">
4755
+ <textarea id="comment-text-${sectionId}-${cardIndex}" placeholder="Ajouter un commentaire..."></textarea>
4756
+ <select id="comment-cat-${sectionId}-${cardIndex}">
4757
+ <option value="clarification">Clarification</option>
4758
+ <option value="correction">Correction</option>
4759
+ <option value="suggestion">Suggestion</option>
4760
+ </select>
4761
+ <button onclick="addInlineComment('${sectionId}', ${cardIndex})">Ajouter</button>
4762
+ </div>
4763
+ </div>
4764
+ `;
4765
+
4766
+ return container;
4767
+ }
4768
+
4769
+ function getCommentsForCard(sectionId, cardIndex) {
4770
+ return (data.comments || []).filter(c =>
4771
+ c.sectionId === sectionId && c.cardIndex === cardIndex
4772
+ );
4773
+ }
4774
+
4775
+ function toggleCommentThread(sectionId, cardIndex) {
4776
+ const thread = document.getElementById('comment-thread-' + sectionId + '-' + cardIndex);
4777
+ if (thread) thread.classList.toggle('visible');
4778
+ }
4779
+
4780
+ function renderCommentItems(sectionId, cardIndex) {
4781
+ const comments = getCommentsForCard(sectionId, cardIndex);
4782
+ if (comments.length === 0) {
4783
+ return '<div style="font-size:0.8rem;color:var(--text-muted);padding:0.5rem 0;font-style:italic;">Aucun commentaire</div>';
4784
+ }
4785
+
4786
+ return comments.map((c, i) => {
4787
+ const initials = (c.author || 'U').substring(0, 2).toUpperCase();
4788
+ const date = c.timestamp ? new Date(c.timestamp).toLocaleDateString('fr-FR', { day: '2-digit', month: '2-digit', hour: '2-digit', minute: '2-digit' }) : '';
4789
+ const globalIndex = data.comments.indexOf(c);
4790
+
4791
+ return `
4792
+ <div class="comment-item">
4793
+ <div class="comment-avatar">${initials}</div>
4794
+ <div class="comment-body">
4795
+ <div class="comment-meta">
4796
+ <span class="comment-author">${c.author || 'Utilisateur'}</span>
4797
+ <span class="comment-date">${date}</span>
4798
+ <span class="comment-category comment-category-${c.category}">${c.category}</span>
4799
+ <span class="comment-status comment-status-${c.status}">${c.status === 'validated' ? 'Valide' : 'A revoir'}</span>
4800
+ </div>
4801
+ <div class="comment-text">${c.content}</div>
4802
+ <div class="comment-actions">
4803
+ <button class="comment-action-btn" onclick="toggleCommentStatus(${globalIndex})">${c.status === 'validated' ? 'Remettre a revoir' : 'Valider'}</button>
4804
+ <button class="comment-action-btn" onclick="deleteComment(${globalIndex})" style="color:var(--error);">Supprimer</button>
4805
+ </div>
4806
+ </div>
4807
+ </div>
4808
+ `;
4809
+ }).join('');
4810
+ }
4811
+
4812
+ function addInlineComment(sectionId, cardIndex) {
4813
+ const textEl = document.getElementById('comment-text-' + sectionId + '-' + cardIndex);
4814
+ const catEl = document.getElementById('comment-cat-' + sectionId + '-' + cardIndex);
4815
+ const content = textEl.value.trim();
4816
+ if (!content) return;
4817
+
4818
+ const comment = {
4819
+ id: 'comment-' + Date.now(),
4820
+ sectionId: sectionId,
4821
+ cardIndex: cardIndex,
4822
+ author: 'Utilisateur',
4823
+ timestamp: new Date().toISOString(),
4824
+ content: content,
4825
+ status: 'to-review',
4826
+ category: catEl.value
4827
+ };
4828
+
4829
+ data.comments.push(comment);
4830
+ textEl.value = '';
4831
+
4832
+ refreshCommentUI(sectionId, cardIndex);
4833
+ renderReviewPanel();
4834
+ autoSave();
4835
+ }
4836
+
4837
+ function toggleCommentStatus(globalIndex) {
4838
+ const comment = data.comments[globalIndex];
4839
+ if (!comment) return;
4840
+ comment.status = comment.status === 'validated' ? 'to-review' : 'validated';
4841
+ refreshCommentUI(comment.sectionId, comment.cardIndex);
4842
+ renderReviewPanel();
4843
+ autoSave();
4844
+ }
4845
+
4846
+ function deleteComment(globalIndex) {
4847
+ const comment = data.comments[globalIndex];
4848
+ if (!comment) return;
4849
+ const sectionId = comment.sectionId;
4850
+ const cardIndex = comment.cardIndex;
4851
+ data.comments.splice(globalIndex, 1);
4852
+ refreshCommentUI(sectionId, cardIndex);
4853
+ renderReviewPanel();
4854
+ autoSave();
4855
+ }
4856
+
4857
+ function refreshCommentUI(sectionId, cardIndex) {
4858
+ // Update comment items
4859
+ const itemsContainer = document.getElementById('comment-items-' + sectionId + '-' + cardIndex);
4860
+ if (itemsContainer) {
4861
+ itemsContainer.innerHTML = renderCommentItems(sectionId, cardIndex);
4862
+ }
4863
+
4864
+ // Update count badge
4865
+ const count = getCommentsForCard(sectionId, cardIndex).length;
4866
+ const container = document.querySelector(`.comment-btn-container[data-section-id="${sectionId}"][data-card-index="${cardIndex}"]`);
4867
+ if (container) {
4868
+ const badge = container.querySelector('.comment-count');
4869
+ if (badge) {
4870
+ badge.textContent = count;
4871
+ badge.className = 'comment-count' + (count === 0 ? ' empty' : '');
4872
+ }
4873
+ }
4874
+ }
4979
4875
 
4980
4876
 
4981
4877
  /* --- 11-review-panel.js --- */
4982
- /* ============================================
4983
- REVIEW PANEL
4984
- ============================================ */
4985
- let reviewPanelOpen = false;
4986
- let reviewFilter = 'all';
4987
-
4988
- function toggleReviewPanel() {
4989
- reviewPanelOpen = !reviewPanelOpen;
4990
- const panel = document.getElementById('reviewPanel');
4991
- const body = document.getElementById('appBody');
4992
- const btn = document.getElementById('reviewToggleBtn');
4993
-
4994
- if (reviewPanelOpen) {
4995
- panel.classList.add('visible');
4996
- body.classList.add('review-open');
4997
- btn.classList.add('active');
4998
- } else {
4999
- panel.classList.remove('visible');
5000
- body.classList.remove('review-open');
5001
- btn.classList.remove('active');
5002
- }
5003
-
5004
- renderReviewPanel();
5005
- }
5006
-
5007
- function filterReviewComments(filter) {
5008
- reviewFilter = filter;
5009
- // Update active filter button
5010
- document.querySelectorAll('.review-filter-btn').forEach(btn => {
5011
- btn.classList.toggle('active', btn.dataset.filter === filter);
5012
- });
5013
- renderReviewPanel();
5014
- }
5015
-
5016
- function renderReviewPanel() {
5017
- const comments = data.comments || [];
5018
-
5019
- // Update header badge
5020
- const toReviewCount = comments.filter(c => c.status === 'to-review').length;
5021
- const badge = document.getElementById('reviewBadge');
5022
- if (badge) {
5023
- badge.textContent = toReviewCount;
5024
- badge.classList.toggle('hidden', toReviewCount === 0);
5025
- }
5026
-
5027
- // Update stats
5028
- const totalEl = document.getElementById('reviewStatTotal');
5029
- const toReviewEl = document.getElementById('reviewStatToReview');
5030
- const validatedEl = document.getElementById('reviewStatValidated');
5031
- if (totalEl) totalEl.textContent = comments.length;
5032
- if (toReviewEl) toReviewEl.textContent = toReviewCount;
5033
- if (validatedEl) validatedEl.textContent = comments.filter(c => c.status === 'validated').length;
5034
-
5035
- // Filter comments
5036
- let filtered = comments;
5037
- if (reviewFilter === 'to-review') {
5038
- filtered = comments.filter(c => c.status === 'to-review');
5039
- } else if (reviewFilter === 'validated') {
5040
- filtered = comments.filter(c => c.status === 'validated');
5041
- }
5042
-
5043
- // Render comment list
5044
- const container = document.getElementById('reviewCommentsList');
5045
- if (!container) return;
5046
-
5047
- if (filtered.length === 0) {
5048
- container.innerHTML = '<div class="review-empty">Aucun commentaire' +
5049
- (reviewFilter !== 'all' ? ' avec ce filtre' : '') + '.</div>';
5050
- return;
5051
- }
5052
-
5053
- container.innerHTML = filtered.map((c, i) => {
5054
- const globalIndex = data.comments.indexOf(c);
5055
- const sectionLabel = getSectionLabel(c.sectionId);
5056
- const date = c.timestamp ? new Date(c.timestamp).toLocaleDateString('fr-FR', { day: '2-digit', month: '2-digit' }) : '';
5057
-
5058
- return `
5059
- <div class="review-comment-item" onclick="navigateToComment('${c.sectionId}', ${c.cardIndex})">
5060
- <div class="review-comment-section">${sectionLabel}</div>
5061
- <div class="review-comment-text">${c.content}</div>
5062
- <div class="review-comment-footer">
5063
- <span class="review-comment-author">${c.author || 'Utilisateur'} - ${date}</span>
5064
- <div class="review-comment-actions">
5065
- <button class="review-action-btn ${c.status === 'validated' ? 'reject' : 'validate'}"
5066
- onclick="event.stopPropagation();toggleCommentStatus(${globalIndex})"
5067
- title="${c.status === 'validated' ? 'Remettre a revoir' : 'Valider'}">
5068
- ${c.status === 'validated' ? 'A revoir' : 'Valider'}
5069
- </button>
5070
- <button class="review-action-btn delete"
5071
- onclick="event.stopPropagation();deleteComment(${globalIndex})"
5072
- title="Supprimer">
5073
- &#10005;
5074
- </button>
5075
- </div>
5076
- </div>
5077
- </div>
5078
- `;
5079
- }).join('');
5080
- }
5081
-
5082
- function getSectionLabel(sectionId) {
5083
- const labels = {
5084
- 'cadrage-context': 'Contexte',
5085
- 'cadrage-stakeholders': 'Parties prenantes',
5086
- 'cadrage-scope': 'Perimetre',
5087
- 'cadrage-risks': 'Risques',
5088
- 'cadrage-success': 'Criteres',
5089
- 'decomp-modules': 'Domaines',
5090
- 'decomp-dependencies': 'Dependances',
5091
- 'consol-interactions': 'Interactions',
5092
- 'consol-permissions': 'Acces',
5093
- 'consol-flows': 'Parcours',
5094
- 'handoff-summary': 'Synthese'
5095
- };
5096
- if (labels[sectionId]) return labels[sectionId];
5097
- if (sectionId.startsWith('module-spec-')) {
5098
- const code = sectionId.replace('module-spec-', '');
5099
- const mod = data.modules.find(m => m.code === code);
5100
- return mod ? mod.name : code;
5101
- }
5102
- // Handle module structure sections (module-struct-{code}-{section})
5103
- const structMatch = sectionId.match(/^module-struct-(.+?)-(.+)$/);
5104
- if (structMatch) {
5105
- const mod = data.modules.find(m => m.code === structMatch[1]);
5106
- const modName = mod ? mod.name : structMatch[1];
5107
- return modName + ' > Structure > ' + structMatch[2];
5108
- }
5109
- // Handle list-based sectionIds (ucList-*, brList-*, entList-*)
5110
- const listMatch = sectionId.match(/^(uc|br|ent)List-(.+)$/);
5111
- if (listMatch) {
5112
- const tabLabels = { uc: 'Cas d\'utilisation', br: 'Regles metier', ent: 'Donnees' };
5113
- const mod = data.modules.find(m => m.code === listMatch[2]);
5114
- const modName = mod ? mod.name : listMatch[2];
5115
- return modName + ' > ' + (tabLabels[listMatch[1]] || listMatch[1]);
5116
- }
5117
- return sectionId;
5118
- }
5119
-
5120
- function navigateToComment(sectionId, cardIndex) {
5121
- // Handle list-based sectionIds (ucList-*, brList-*, entList-*) → navigate to module + tab
5122
- const listMatch = sectionId.match(/^(uc|br|ent)List-(.+)$/);
5123
- if (listMatch) {
5124
- const [, tabType, moduleCode] = listMatch;
5125
- showSection('module-spec-' + moduleCode);
5126
- setTimeout(() => {
5127
- switchTab(moduleCode, tabType);
5128
- scrollToCommentThread(sectionId, cardIndex);
5129
- }, 150);
5130
- } else {
5131
- showSection(sectionId);
5132
- scrollToCommentThread(sectionId, cardIndex);
5133
- }
5134
- }
5135
-
5136
- function scrollToCommentThread(sectionId, cardIndex) {
5137
- setTimeout(() => {
5138
- const thread = document.getElementById('comment-thread-' + sectionId + '-' + cardIndex);
5139
- if (thread && !thread.classList.contains('visible')) {
5140
- thread.classList.add('visible');
5141
- }
5142
- const container = document.querySelector(`.comment-btn-container[data-section-id="${sectionId}"][data-card-index="${cardIndex}"]`);
5143
- if (container) {
5144
- container.scrollIntoView({ behavior: 'smooth', block: 'center' });
5145
- }
5146
- }, 100);
5147
- }
4878
+ /* ============================================
4879
+ REVIEW PANEL
4880
+ ============================================ */
4881
+ let reviewPanelOpen = false;
4882
+ let reviewFilter = 'all';
4883
+
4884
+ function toggleReviewPanel() {
4885
+ reviewPanelOpen = !reviewPanelOpen;
4886
+ const panel = document.getElementById('reviewPanel');
4887
+ const body = document.getElementById('appBody');
4888
+ const btn = document.getElementById('reviewToggleBtn');
4889
+
4890
+ if (reviewPanelOpen) {
4891
+ panel.classList.add('visible');
4892
+ body.classList.add('review-open');
4893
+ btn.classList.add('active');
4894
+ } else {
4895
+ panel.classList.remove('visible');
4896
+ body.classList.remove('review-open');
4897
+ btn.classList.remove('active');
4898
+ }
4899
+
4900
+ renderReviewPanel();
4901
+ }
4902
+
4903
+ function filterReviewComments(filter) {
4904
+ reviewFilter = filter;
4905
+ // Update active filter button
4906
+ document.querySelectorAll('.review-filter-btn').forEach(btn => {
4907
+ btn.classList.toggle('active', btn.dataset.filter === filter);
4908
+ });
4909
+ renderReviewPanel();
4910
+ }
4911
+
4912
+ function renderReviewPanel() {
4913
+ const comments = data.comments || [];
4914
+
4915
+ // Update header badge
4916
+ const toReviewCount = comments.filter(c => c.status === 'to-review').length;
4917
+ const badge = document.getElementById('reviewBadge');
4918
+ if (badge) {
4919
+ badge.textContent = toReviewCount;
4920
+ badge.classList.toggle('hidden', toReviewCount === 0);
4921
+ }
4922
+
4923
+ // Update stats
4924
+ const totalEl = document.getElementById('reviewStatTotal');
4925
+ const toReviewEl = document.getElementById('reviewStatToReview');
4926
+ const validatedEl = document.getElementById('reviewStatValidated');
4927
+ if (totalEl) totalEl.textContent = comments.length;
4928
+ if (toReviewEl) toReviewEl.textContent = toReviewCount;
4929
+ if (validatedEl) validatedEl.textContent = comments.filter(c => c.status === 'validated').length;
4930
+
4931
+ // Filter comments
4932
+ let filtered = comments;
4933
+ if (reviewFilter === 'to-review') {
4934
+ filtered = comments.filter(c => c.status === 'to-review');
4935
+ } else if (reviewFilter === 'validated') {
4936
+ filtered = comments.filter(c => c.status === 'validated');
4937
+ }
4938
+
4939
+ // Render comment list
4940
+ const container = document.getElementById('reviewCommentsList');
4941
+ if (!container) return;
4942
+
4943
+ if (filtered.length === 0) {
4944
+ container.innerHTML = '<div class="review-empty">Aucun commentaire' +
4945
+ (reviewFilter !== 'all' ? ' avec ce filtre' : '') + '.</div>';
4946
+ return;
4947
+ }
4948
+
4949
+ container.innerHTML = filtered.map((c, i) => {
4950
+ const globalIndex = data.comments.indexOf(c);
4951
+ const sectionLabel = getSectionLabel(c.sectionId);
4952
+ const date = c.timestamp ? new Date(c.timestamp).toLocaleDateString('fr-FR', { day: '2-digit', month: '2-digit' }) : '';
4953
+
4954
+ return `
4955
+ <div class="review-comment-item" onclick="navigateToComment('${c.sectionId}', ${c.cardIndex})">
4956
+ <div class="review-comment-section">${sectionLabel}</div>
4957
+ <div class="review-comment-text">${c.content}</div>
4958
+ <div class="review-comment-footer">
4959
+ <span class="review-comment-author">${c.author || 'Utilisateur'} - ${date}</span>
4960
+ <div class="review-comment-actions">
4961
+ <button class="review-action-btn ${c.status === 'validated' ? 'reject' : 'validate'}"
4962
+ onclick="event.stopPropagation();toggleCommentStatus(${globalIndex})"
4963
+ title="${c.status === 'validated' ? 'Remettre a revoir' : 'Valider'}">
4964
+ ${c.status === 'validated' ? 'A revoir' : 'Valider'}
4965
+ </button>
4966
+ <button class="review-action-btn delete"
4967
+ onclick="event.stopPropagation();deleteComment(${globalIndex})"
4968
+ title="Supprimer">
4969
+ &#10005;
4970
+ </button>
4971
+ </div>
4972
+ </div>
4973
+ </div>
4974
+ `;
4975
+ }).join('');
4976
+ }
4977
+
4978
+ function getSectionLabel(sectionId) {
4979
+ const labels = {
4980
+ 'cadrage-context': 'Contexte',
4981
+ 'cadrage-stakeholders': 'Parties prenantes',
4982
+ 'cadrage-scope': 'Perimetre',
4983
+ 'cadrage-success': 'Criteres',
4984
+ 'decomp-modules': 'Domaines',
4985
+ 'decomp-dependencies': 'Dependances',
4986
+ 'consol-interactions': 'Interactions',
4987
+ 'consol-permissions': 'Acces',
4988
+ 'consol-flows': 'Parcours',
4989
+ 'handoff-summary': 'Synthese'
4990
+ };
4991
+ if (labels[sectionId]) return labels[sectionId];
4992
+ if (sectionId.startsWith('module-spec-')) {
4993
+ const code = sectionId.replace('module-spec-', '');
4994
+ const mod = data.modules.find(m => m.code === code);
4995
+ return mod ? mod.name : code;
4996
+ }
4997
+ // Handle module structure sections (module-struct-{code}-{section})
4998
+ const structMatch = sectionId.match(/^module-struct-(.+?)-(.+)$/);
4999
+ if (structMatch) {
5000
+ const mod = data.modules.find(m => m.code === structMatch[1]);
5001
+ const modName = mod ? mod.name : structMatch[1];
5002
+ return modName + ' > Structure > ' + structMatch[2];
5003
+ }
5004
+ // Handle list-based sectionIds (ucList-*, brList-*, entList-*)
5005
+ const listMatch = sectionId.match(/^(uc|br|ent)List-(.+)$/);
5006
+ if (listMatch) {
5007
+ const tabLabels = { uc: 'Cas d\'utilisation', br: 'Regles metier', ent: 'Donnees' };
5008
+ const mod = data.modules.find(m => m.code === listMatch[2]);
5009
+ const modName = mod ? mod.name : listMatch[2];
5010
+ return modName + ' > ' + (tabLabels[listMatch[1]] || listMatch[1]);
5011
+ }
5012
+ return sectionId;
5013
+ }
5014
+
5015
+ function navigateToComment(sectionId, cardIndex) {
5016
+ // Handle list-based sectionIds (ucList-*, brList-*, entList-*) → navigate to module + tab
5017
+ const listMatch = sectionId.match(/^(uc|br|ent)List-(.+)$/);
5018
+ if (listMatch) {
5019
+ const [, tabType, moduleCode] = listMatch;
5020
+ showSection('module-spec-' + moduleCode);
5021
+ setTimeout(() => {
5022
+ switchTab(moduleCode, tabType);
5023
+ scrollToCommentThread(sectionId, cardIndex);
5024
+ }, 150);
5025
+ } else {
5026
+ showSection(sectionId);
5027
+ scrollToCommentThread(sectionId, cardIndex);
5028
+ }
5029
+ }
5030
+
5031
+ function scrollToCommentThread(sectionId, cardIndex) {
5032
+ setTimeout(() => {
5033
+ const thread = document.getElementById('comment-thread-' + sectionId + '-' + cardIndex);
5034
+ if (thread && !thread.classList.contains('visible')) {
5035
+ thread.classList.add('visible');
5036
+ }
5037
+ const container = document.querySelector(`.comment-btn-container[data-section-id="${sectionId}"][data-card-index="${cardIndex}"]`);
5038
+ if (container) {
5039
+ container.scrollIntoView({ behavior: 'smooth', block: 'center' });
5040
+ }
5041
+ }, 100);
5042
+ }
5148
5043
 
5149
5044
  </script>
5150
5045
  </body>